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