You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
15 KiB
Bash

#!/bin/bash
#Author: Edoardo Coli
#
#Requirements: bash tshark
#Tested with: GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
#Tested with: TShark (Wireshark) 3.6.7 (Git v3.6.7 packaged as 3.6.7-1~ubuntu18.04.0+wiresharkdevstable)
#Temporary files to save data
_FILE_TMP=".MACrandom.tmp"
_FILE2_TMP=".MAClistall.tmp"
_FILE3_TMP=".MACprobe.tmp"
#Reference strings
_FILE_REF=''
_SSID_REF=''
#Flags for optional variables
_ENHANCED_FLAG='false'
_VERBOSE_FLAG='false'
_UNIQ_FLAG='false'
_PROBE_FLAG='false'
#Global parametric variable
_UP_TO=50000
#Counters to show the results
_PCKS_CNTR=0
_PCKS_WLAN_CNTR=0
_PCKS_SSID_CNTR=0
_SCOPE_ALL_CNTR=0
_SCOPE_RAND_CNTR=0
_SCOPE_FIX_CNTR=0
#Set the error color to red
ERROR="\e[31m"
#Set the warning color to yellow
WARNING="\e[33m"
#Set color to highlight the results
LOOKGOOD="\e[0;38;5;208m"
#Set the default color
DEFAULT="\e[0m"
### error_msg(program_name, msg)
function error_msg()
{
echo "Error: $2"
echo "Usage: $1 [OPTIONS]"
echo "Try '$1 -h' for more information."
}
### verbose_msg(msg)
function verbose_msg()
{
if [[ $_VERBOSE_FLAG == 'true' ]]; then
echo -e "${LOOKGOOD}$1${DEFAULT}"
fi
}
### usage(program_name)
function usage()
{
echo "Usage: $1 [OPTIONS]"
echo "Specifies a file to analyze if not passed through redirection."
echo "Pipe(|) have priority over flag -f."
echo "Redirection(<) have priority over pipe(|)."
echo "WARNING: When using pipe redirection for data stream remember to use the -U and -w flags."
echo " The "-U" flag instructs tcpdump to write packets to the output file immediately (unbuffered mode)."
echo " (this should avoid having \"appears to have been cut short in the middle of a packet\")"
echo " The "-w" flag specifies the output file where packets will be saved."
echo
echo "Options:"
echo " -e, Option to discriminate and merge different MAC addresses (NOT YET DEVELOPED)"
echo " -f, Set the file to analyze"
echo " -i, Set SSID filter to analyze frames"
echo " -n, Number of packets to analyze, in case of file data input (starting from the beginning) ~~TODO test with data stream"
echo " -p, Option to pipe in data stream, without a pipe is ignored"
echo " -u, Option to not consider duplicates in MAC count"
echo " -v, Option for enable verbose messages"
echo
echo "Example:"
echo " sudo tcpdump -i <interface> -U -w $_FILE3_TMP | $0 -p"
echo " $0 < file.pcap"
echo " cat file.pcap | $0"
echo " $0 -f file.pcap"
echo
}
### Behavior when received SIGINT
function handle_sigint()
{
# verbose_msg "SIGINT received..."
# verbose_msg "killing main process"
#Cleanup code
rm -f "$_FILE_TMP"
rm -f "$_FILE2_TMP"
rm -f "$_FILE3_TMP"
rm -f "$lock_print"
kill -9 $_PROCESS_ID #forcefully terminate the process, and any subprocesses or child processes, with the ID using the SIGKILL signal. (sometimes it's not enough, why?)
exit 0
}
### check_ret(prog_name, val_ret)
function check_ret()
{
notgood=0
case $1 in
"filtering_away")
if [ $2 -ne 0 ]; then
notgood=1
fi
;;
"print_results")
if [ $2 -ne 0 ]; then
notgood=1
fi
;;
*) echo -e "${WARNING}Setup success value returned for function \"$1\"${DEFAULT}"
return 1;;
esac
if [ $notgood -eq 1 ]; then
echo -e "${ERROR}Problem with the execution of \"$1\", exited with status $2${DEFAULT}"
fi
}
###
function filtering_away()
{
scope='wlan.ta' #filter for the main scope of divide randomized MAC and fixed ones.
filterSSID=''
#(PARALLEL EXECUTION)
if ! [[ $_SSID_REF == '' ]]; then
filterSSID="&& (wlan.ssid == $_SSID_REF)" #filter from command line to narrow the scope with a specific SSID.
fi
filter="($scope) && (wlan) $filterSSID" #save the complete filter which wanna use to split the data.
rm -f $_FILE_TMP $_FILE2_TMP #just as a precaution.
_TMP=$(mktemp -d) #creates a unique temporary directory in /tmp/ folder.
mkfifo $_TMP/pipe1 $_TMP/pipe2 $_TMP/pipe3 $_TMP/pipe4 #creates named pipes inside a temporary directory created before.
verbose_msg "Counting packets in $_FILE_REF"
verbose_msg "Counting packets with field 'wlan' in $_FILE_REF"
if ! [[ $_SSID_REF == '' ]]; then
verbose_msg "Counting packets with field 'wlan.ssid == $_SSID_REF' in $_FILE_REF"
fi
verbose_msg "Counting packets with filter '$filter' in $_FILE_REF"
(tshark -r $_FILE_REF -T fields -e frame.number -c $_UP_TO | wc -l >$_TMP/pipe1) & #the symbol '&' runs the command in the background, allowing other commands to be run in parallel.
(tshark -r $_FILE_REF -Y "wlan" -T fields -e frame.number -c $_UP_TO | wc -l >$_TMP/pipe2) & #the symbol '&' runs the command in the background, allowing other commands to be run in parallel.
if ! [[ $_SSID_REF == '' ]]; then
(tshark -r $_FILE_REF -Y "wlan.ssid == $_SSID_REF" -T fields -e frame.number -c $_UP_TO | wc -l >$_TMP/pipe3) & #the symbol '&' runs the command in the background, allowing other commands to be run in parallel.
else
(echo "(NaN)" >$_TMP/pipe3) &
fi
if [[ $_UNIQ_FLAG == 'true' ]]; then
(tshark -r $_FILE_REF -Y "$filter" -T fields -e wlan.ta -c $_UP_TO | tr '[:lower:]' '[:upper:]' | sort | uniq | tee >(wc -l > $_TMP/pipe4) >"$_FILE2_TMP") & #the symbol '&' runs the command in the background, allowing other commands to be run in parallel.
else
(tshark -r $_FILE_REF -Y "$filter" -T fields -e wlan.ta -e wlan.fc.type -c $_UP_TO | tr '[:lower:]' '[:upper:]' | tee >(wc -l > $_TMP/pipe4) >"$_FILE2_TMP") & #the symbol '&' runs the command in the background, allowing other commands to be run in parallel.
fi
while read line; do
verbose_msg " '$line'"
_PCKS_CNTR=$(echo $line | cut -d ' ' -f 1)
_PCKS_WLAN_CNTR=$(echo $line | cut -d ' ' -f 2)
_PCKS_SSID_CNTR=$(echo $line | cut -d ' ' -f 3)
_SCOPE_ALL_CNTR=$(echo $line | cut -d ' ' -f 4)
done < <(paste -d ' ' $_TMP/pipe1 $_TMP/pipe2 $_TMP/pipe3 $_TMP/pipe4) #the 'paste' command merges the lines of both named pipes and separates them with a space. the '< <' operator to redirect the merged output as input to the while loop.
rm -rf $_TMP #remove the temporary directory and all its contents recursively (-r) and without prompting (-f).
verbose_msg "=> $_PCKS_CNTR for no filter"
verbose_msg "=> $_PCKS_WLAN_CNTR for filter 'wlan'"
if ! [[ $_SSID_REF == '' ]]; then
verbose_msg "=> $_PCKS_SSID_CNTR for filter 'wlan.ssid == $_SSID_REF'"
fi
verbose_msg "=> $_SCOPE_ALL_CNTR for filter '$filter'"
if [[ $_UNIQ_FLAG == 'true' ]]; then
if [[ $_VERBOSE_FLAG == 'true' ]]; then
_SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | sort | uniq | tee >(wc -l) >"$_FILE_TMP") #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). sort+uniq remove the duplicates(seems useless but necessary). tee split the data in the pipe.
else
_SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | sort | uniq | wc -l) #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). sort+uniq remove the duplicates(seems useless but necessary).
fi
else
if [[ $_VERBOSE_FLAG == 'true' ]]; then
_SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | tee >(wc -l) >"$_FILE_TMP") #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). tee split the data in the pipe.
else
_SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | wc -l) #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx).
fi
fi
#(SEQUENTIAL EXECUTION)
# verbose_msg "Counting packets in $_FILE_REF"
# _PCKS_CNTR=$(tshark -r $_FILE_REF -T fields -e frame.number -c $_UP_TO | wc -l) #for details see 'man tshark'.
# verbose_msg "=> $_PCKS_CNTR"
# verbose_msg "Counting packets with field 'wlan' in $_FILE_REF"
# _PCKS_WLAN_CNTR=$(tshark -r $_FILE_REF -Y "wlan" -T fields -e frame.number -c $_UP_TO | wc -l) #for details see 'man tshark'.
# verbose_msg "=> $_PCKS_WLAN_CNTR"
# if ! [[ $_SSID_REF == '' ]]; then
# filterSSID="&& (wlan.ssid == $_SSID_REF)" #filter from command line to narrow the scope with a specific SSID.
# verbose_msg "Counting packets with field 'wlan.ssid == $_SSID_REF' in $_FILE_REF"
# _PCKS_SSID_CNTR=$(tshark -r $_FILE_REF -Y "wlan.ssid == $_SSID_REF" -T fields -e frame.number -c $_UP_TO | wc -l) #for details see 'man tshark'.
# verbose_msg "=> $_PCKS_SSID_CNTR"
# fi
# filter="($scope) && (wlan) $filterSSID" #save the complete filter which wanna use to split the data.
# rm -f $_FILE_TMP $_FILE2_TMP #just as a precaution.
# if [[ $_UNIQ_FLAG == 'true' ]]; then
# verbose_msg "Counting packets with filter '$filter' in $_FILE_REF"
# _SCOPE_ALL_CNTR=$(tshark -r $_FILE_REF -Y "$filter" -T fields -e wlan.ta -c $_UP_TO | tr '[:lower:]' '[:upper:]' | sort | uniq | tee >(wc -l) >"$_FILE2_TMP") #for details see 'man tshark'. tr swap all upper-case. sort+uniq remove the duplicates. tee split the data in the pipe.
# verbose_msg "=> $_SCOPE_ALL_CNTR"
# if [[ $_VERBOSE_FLAG == 'true' ]]; then
# _SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | sort | uniq | tee >(wc -l) >"$_FILE_TMP") #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). sort+uniq remove the duplicates(seems useless but necessary). tee split the data in the pipe.
# else
# _SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | sort | uniq | wc -l) #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). sort+uniq remove the duplicates(seems useless but necessary).
# fi
# else
# verbose_msg "Counting packets with filter '$filter' in $_FILE_REF"
# _SCOPE_ALL_CNTR=$(tshark -r $_FILE_REF -Y "$filter" -T fields -e wlan.ta -e wlan.fc.type -c $_UP_TO | tr '[:lower:]' '[:upper:]' | tee >(wc -l) >"$_FILE2_TMP") #for details see 'man tshark'. tr swap all upper-case. tee split the data in the pipe.
# verbose_msg "=> $_SCOPE_ALL_CNTR"
# if [[ $_VERBOSE_FLAG == 'true' ]]; then
# _SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | tee >(wc -l) >"$_FILE_TMP") #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx). tee split the data in the pipe.
# else
# _SCOPE_RAND_CNTR=$(cut -c 1-17 $_FILE2_TMP | awk '/^.[AE26]:..:..:..:..:../{print $1}' | wc -l) #cut print selected parts of line. awk is used to consider only MAC specified(x[A E 2 6]:xx:xx:xx:xx:xx).
# fi
# fi
_SCOPE_FIX_CNTR=$(expr $_SCOPE_ALL_CNTR - $_SCOPE_RAND_CNTR)
return 0;
}
###
function print_results()
{
if ! [ -t 0 ]; then
echo "Total Number of Packets captured: $_PCKS_CNTR"
else
echo "Total Number of Packets captured in \"$_FILE_REF\": $_PCKS_CNTR"
fi
echo
echo -n "Number of Frame Structure of IEEE 802.11"
if [[ $_UNIQ_FLAG == 'false' ]]; then
echo -n " (with field 'wlan.ta' $_SCOPE_ALL_CNTR)"
fi
echo -n " : $_PCKS_WLAN_CNTR"
if [[ $_PCKS_WLAN_CNTR -eq 0 || $_PCKS_CNTR -eq 0 ]]; then
echo
else
echo "(~$(expr $_PCKS_WLAN_CNTR \* 100 / $_PCKS_CNTR )%)"
fi
if ! [[ $_SSID_REF == '' ]]; then
echo "-->Taking into account only those with \"SSID=$_SSID_REF\": $_PCKS_SSID_CNTR"
fi
echo -n "----> with randomized MAC: $_SCOPE_RAND_CNTR"
if [[ $_SCOPE_RAND_CNTR -eq 0 || $_PCKS_WLAN_CNTR -eq 0 ]]; then
echo
else
echo "(~$(expr $_SCOPE_RAND_CNTR \* 100 / $_SCOPE_ALL_CNTR )%)"
fi
echo "----> with fixed MAC: $_SCOPE_FIX_CNTR"
if [[ $_VERBOSE_FLAG == 'true' ]]; then
N=10
if [ $(wc -l < $_FILE_TMP) -gt $N ]; then
echo && echo -e "${LOOKGOOD}$_FILE_TMP${DEFAULT}" && head -n $N "$_FILE_TMP" && echo "..."
elif [ $(wc -l < $_FILE_TMP) -gt 0 ]; then
echo && echo -e "${LOOKGOOD}$_FILE_TMP${DEFAULT}" && head -n $N "$_FILE_TMP"
fi
# rm $_FILE_TMP
if [ $(wc -l < $_FILE2_TMP) -gt $N ] && [[ $_UNIQ_FLAG == 'false' ]]; then
echo && echo -e "${LOOKGOOD}$_FILE2_TMP${DEFAULT}" && echo "[MAC Address] [Frame Control Type:]" && echo " [0-Management Frame, 1-Control Frame, 2-Data Frame, 3-Extension Frame, (4+)-PV1 Reserved]" && head -n $N "$_FILE2_TMP" && echo "..."
elif [ $(wc -l < $_FILE2_TMP) -gt $N ]; then
echo && echo -e "${LOOKGOOD}$_FILE2_TMP${DEFAULT}" && head -n $N "$_FILE2_TMP" && echo "..."
elif [ $(wc -l < $_FILE2_TMP) -gt 0 ]; then
echo && echo -e "${LOOKGOOD}$_FILE2_TMP${DEFAULT}" && head -n $N "$_FILE2_TMP"
fi
# rm $_FILE2_TMP
fi
echo
return 0
}
### START EXECUTION
_PROCESS_ID=$$ #save the process ID of the current shell
if [ $# -le 0 ] && [ -t 0 ]; then #if the number of arguments is less than or equal to 0 and we don't have a redirection.
error_msg $0 "Nothing has been passed to analyze"
exit 1
else
while getopts 'ef:hi:n:puv' flag; do #colon(:) to indicate that the flag has one argument.
case "${flag}" in
e) _ENHANCED_FLAG='true' ;;
f) _FILE_REF="${OPTARG}" ;;
h) usage $0
exit 0;;
i) _SSID_REF="${OPTARG}" ;;
n) _UP_TO="${OPTARG}" ;;
p) _PROBE_FLAG='true' ;;
u) _UNIQ_FLAG='true' ;;
v) _VERBOSE_FLAG='true' ;;
*) exit 1;;
esac
done
fi
if ! [ -t 0 ]; then #checks if the descriptor is opened with a redirection, regardless of type of redirection used.
if [ -p /dev/stdin ]; then #checks if there is a pipe redirection. Redirection from a command and not from a file.
if [[ $_PROBE_FLAG == 'true' ]]; then #checks if we want to process a data stream (from tcpdump).
trap handle_sigint SIGINT #set up a trap for SIGINT
lock_print=$(mktemp) #creates a unique temporary file in /tmp/ folder.
_FILE_REF="$_FILE3_TMP"
sleep 2 #waiting to have something to analyze.
echo -ne "\033[2J\033[H" #the "\033[2J" sequence clear the terminal screen, the "\033[H" sequence moves the cursor to the top left corner of the screen.
while true
do
filtering_away > $lock_print
check_ret filtering_away $?
print_results >> $lock_print
check_ret print_results $?
echo -e "\033[2J\033[H" #the "\033[2J" sequence clear the terminal screen, the "\033[H" sequence moves the cursor to the top left corner of the screen.
echo "$(cat $lock_print)" #The "$()" syntax is used to execute the command inside the parentheses and return the result as a string (used to handle print cases TO REVIEW)
done
else
_FILE_REF=$(mktemp) #creates a unique temporary file in /tmp/ folder.
cat /dev/stdin > "$_FILE_REF"
fi
else
if head -c4 | od -t x4 -N4 | grep -q -e "a1b2c3d4" -e "d4c3b2a1" -e "0a0d0d0a"; then #handled only pcap, pcapng files (not erf ...) using magic number of file.
_FILE_REF=$(mktemp) #creates a unique temporary file in /tmp/ folder.
cat /dev/stdin > "$_FILE_REF"
else
error_msg $0 "Not a pcap/pcapng file"
exit 1;
fi
fi
else
if ! [[ $_FILE_REF == '' ]]; then
if ! head -c4 $_FILE_REF | od -t x4 -N4 | grep -q -e "a1b2c3d4" -e "d4c3b2a1" -e "0a0d0d0a"; then #handled only pcap, pcapng files (not erf ...) using magic number of file.
error_msg $0 "'$_FILE_REF' not a pcap/pcapng file"
exit 1;
fi
else
error_msg $0 "Insert a file using -f flag"
exit 1;
fi
fi
filtering_away
check_ret filtering_away $?
if ! [ -t 0 ]; then
rm -f "$_FILE_REF"
fi
print_results
check_ret print_results $?
exit 0