diff --git a/ansible/tasks/playbook_GPS_sync b/ansible/tasks/playbook_GPS_sync.yml similarity index 88% rename from ansible/tasks/playbook_GPS_sync rename to ansible/tasks/playbook_GPS_sync.yml index 8e71f58752ba100ca526358f8838aa259ceb63fb..6aca59afc8693fa83f4e4ddf3132f52b7a5663d4 100644 --- a/ansible/tasks/playbook_GPS_sync +++ b/ansible/tasks/playbook_GPS_sync.yml @@ -19,17 +19,9 @@ - name: pps-tools installation command: sudo apt install pps-tools -y - #- name: python-gps installation - # command: sudo apt install python-gps -y - - name: systemctl enable gpsd command: sudo systemctl enable gpsd.socket - #- name: Copy a new config gpsd device functionality - # copy: - # src: /home/pi/ansible/config.txt - # dest: /boot/config.txt - - name: Copy a new config gpsd device functionality blockinfile: | dest=/boot/config.txt @@ -61,7 +53,6 @@ - name: ntp uninstall command: sudo apt remove ntp -y - - name: chrony installation command: sudo apt install chrony -y diff --git a/ansible/tasks/playbook_NIC_config b/ansible/tasks/playbook_NIC_config.yml similarity index 100% rename from ansible/tasks/playbook_NIC_config rename to ansible/tasks/playbook_NIC_config.yml diff --git a/ansible/tasks/playbook_SSH_keygen b/ansible/tasks/playbook_SSH_keygen.yml similarity index 100% rename from ansible/tasks/playbook_SSH_keygen rename to ansible/tasks/playbook_SSH_keygen.yml diff --git a/ansible/tasks/playbook_hostname b/ansible/tasks/playbook_hostname.yml similarity index 100% rename from ansible/tasks/playbook_hostname rename to ansible/tasks/playbook_hostname.yml diff --git a/ansible/tasks/playbook_hosts b/ansible/tasks/playbook_hosts.yml similarity index 74% rename from ansible/tasks/playbook_hosts rename to ansible/tasks/playbook_hosts.yml index 198877e7e945d5a249d4786d0c3a9b7c8b8645db..8eacb9e5095b6a1d43b0ac48b2a61a552daf6939 100644 --- a/ansible/tasks/playbook_hosts +++ b/ansible/tasks/playbook_hosts.yml @@ -1,9 +1,6 @@ - name: host file update - Local DNS setup across all the servers hosts: sniffers gather_facts: yes - #become: yes - #become_user: root - #become_exe: sudo su - tasks: - name: Update the /etc/hosts file with node name @@ -14,9 +11,7 @@ dest: "/etc/hosts" regexp: ".*\t{{ hostvars[item]['ansible_hostname']}}\t{{ hostvars[item]['ansible_hostname']}}" line: "{{ hostvars[item]['ansible_default_ipv4']['address'] }}\t{{ hostvars[item]['ansible_hostname']}}\t{{ hostvars[item]['ansible_hostname']}}" - #line: "{{ hostvars[item]['ansible_env'].SSH_CONNECTION.split(' ')[2] }}\t{{ hostvars[item]['ansible_hostname']}}\t{{ hostvars[item]['ansible_hostname']}}" state: present - #backup: yes register: etchostsupdate when: ansible_hostname != "item" or ansible_hostname == "item" diff --git a/ansible/tasks/playbook_scapy-sniffer b/ansible/tasks/playbook_scapy-sniffer_GPS.yml similarity index 64% rename from ansible/tasks/playbook_scapy-sniffer rename to ansible/tasks/playbook_scapy-sniffer_GPS.yml index fbf054eb7ff5b70aba9f8ede4271ae8f60536e67..c237893a9f911ea838053a6ea7d39dccc3216d73 100644 --- a/ansible/tasks/playbook_scapy-sniffer +++ b/ansible/tasks/playbook_scapy-sniffer_GPS.yml @@ -1,11 +1,17 @@ - hosts: sniffers - ignore_unreachable: true + user: gta + #become: yes + #become_user: root - vars: - cap_file: packet_capture_{{ansible_hostname}}_{{ ansible_date_time['epoch'] }}.pcap + vars_prompt: + - name: _file + prompt: Enter the folder name to save traces in the sniffer + private: no - vars_prompt: + - name: _location + prompt: Enter the location of the experiments + private: no - name: _hour prompt: Enter an hour to start the experiment @@ -31,7 +37,7 @@ - name: _channel prompt: Please specify the channel (integer between 1-11, default=system). For default just press enter - default: '' + default: 1 private: no - name: _hash_function @@ -44,23 +50,20 @@ default: 15 private: no - - name: dest_folder - prompt: Please specify the destination folder (location on remote server e.g. /var/tmp/) - private: no - - #- name: filter - # prompt: Please specify the tcpdump filter (e.g. host 10.10.10.10). For no filter just press enter - # default: "" - # private: no + tasks: + - name: Create Folder + file: + path: "{{ _file }}" + owner: gta + group: gta + #mode: 0755 + state: directory - tasks: - name: start scapy-sniffer + cron: - name: "ansible_scapy-sniffer" - #state: present + name: "ansible_scapy-sniffer {{ _hour }} {{ _minute }} {{ _channel }}" minute: "{{ _minute }}" hour: "{{ _hour }}" - job: "sudo python3 sniffers/scapy-sniffer/sniffer.py -i {{ _interface }} -c {{ _channel }} -e {{ _hash_function}} -p {{ _hash_pattern }} -t {{ _timeout }} -w {{ dest_folder}}/{{ cap_file }}" - #user: root - #become: true + job: "sudo python3 /home/gta/sniffers/scapy-sniffer/sniffer_GPS.py -F {{ _file }} -L {{ _location }} -i {{ _interface }} -c {{ _channel }} -f {{ _filter }} -e {{ _hash_function}} -p {{ _hash_pattern }} -t {{ _timeout }}" \ No newline at end of file diff --git a/utils/sniffer_GPS.py b/utils/sniffer_GPS.py new file mode 100644 index 0000000000000000000000000000000000000000..fe4045f51e0c0a8cb7590e84e3c99cf019529c44 --- /dev/null +++ b/utils/sniffer_GPS.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +# +# sniffer.py +# +# Description: +# Passive wi-fi sniffer using scapy library. Usage requires interface in Monitor mode and root permissions. +# To see options type './sniffer -h' or './sniffer.py --help' + +import sys +import logging +from datetime import datetime +import argparse +import hashlib +from pathlib import Path +import Utils +import platform + +# Libraries GPS +#import serial +import time +import string +#import pynmea2 + +from gps import * +import inspect + +# Libraries Scapy +import scapy.all as scapy + +dev_name = platform.node() +# GPS variables +GPS_lat = ['NO_GPS'] +GPS_lon = ['NO_GPS'] +gpsd=gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) + +# GPS function +def GPS_coords(): + while True: + report=gpsd.next() + if report['class']=='TPV': + lat=str(getattr(report,'lat',0.0)) + lon=str(getattr(report,'lon',0.0)) + return lat,lon + +# Encryption functions +# Functions that define possible encryption operations with the source address +def MD5Hash(data): + return hashlib.md5(data).hexdigest()[:12] if not data is None else None + +def SHA256Hash(data): + return hashlib.sha256(data).hexdigest()[:12] if not data is None else None + +# Function handler for each captured packet + +def PacketHandler(packet): + global hashPattern, HashFunction, logger, pcapWriter#, stopCapture + global fieldControlMAC1, fieldControlMAC2, fieldControlMAC3, fieldControlSSID + + srcMac = packet.addr2 + srcSSID = packet[3].info + # Writes the hash result received in python to a format that directly relates to the MAC address ouput + # Output MAC address is weird when set as bytes, must convert to string format prior to assignment + # The same does not happen to the SSID + if HashFunction: + if (hashPattern & fieldControlMAC1) and packet.addr1 and packet.addr1 != "ff:ff:ff:ff:ff:ff": + packet.addr1 = str(HashFunction(packet.addr1.encode('utf-8'))) + packet.addr1 = packet.addr1[0:] + ":" +packet.addr1[2:4] + ":" +packet.addr1[4:6] + ":" +packet.addr1[6:8] + ":" +packet.addr1[8:10] + ":" +packet.addr1[10:] + + if (hashPattern & fieldControlMAC2) and packet.addr2 and packet.addr2 != "ff:ff:ff:ff:ff:ff": + packet.addr2 = str(HashFunction(packet.addr2.encode('utf-8'))) + packet.addr2 = packet.addr2[0:] + ":" +packet.addr2[2:4] + ":" +packet.addr2[4:6] + ":" +packet.addr2[6:8] + ":" +packet.addr2[8:10] + ":" +packet.addr2[10:] + + if (hashPattern & fieldControlMAC3) and packet.addr3 and packet.addr3 != "ff:ff:ff:ff:ff:ff": + packet.addr3 = str(HashFunction(packet.addr3.encode('utf-8'))) + packet.addr3 = packet.addr3[0:] + ":" +packet.addr3[2:4] + ":" +packet.addr3[4:6] + ":" +packet.addr3[6:8] + ":" +packet.addr3[8:10] + ":" +packet.addr3[10:] + + # Hashes SSID + if (hashPattern & fieldControlSSID): + packet[3].info = HashFunction(packet[3].info) # Gets to the SSID Value + packet[3].len = len(packet[3].info) + + pcapWriter.write(packet) + +# Global variables + +#stopCapture = False # Variable that stores the signal state +logger = None # Stores the logger object +availableHashFunctions = ['None','MD5','SHA256'] +hashFunctions = [None,MD5Hash,SHA256Hash] +hashPattern = 15 +time_out = None # Variable to define stop sniffing after a given time + +# Field control for hashing +fieldControlMAC1 = 8 +fieldControlMAC2 = 4 +fieldControlMAC3 = 2 +fieldControlSSID = 1 +# +GPS_parsed = GPS_coords() +GPS_lat=GPS_parsed[0] +GPS_lon=GPS_parsed[1] + +def int_range(mini,maxi): + """Return function handle of an argument type function for + ArgumentParser checking an integer range: mini <= arg <= maxi + mini - minimum acceptable argument + maxi - maximum acceptable argument""" + + # Define the function with default arguments + def int_range_checker(arg): + """New Type function for argparse - a float within predefined range.""" + + try: + f = int(arg) + except ValueError: + raise argparse.ArgumentTypeError("must be a floating point number") + if f < mini or f > maxi: + raise argparse.ArgumentTypeError("must be in range [" + str(mini) + ", " + str(maxi)+"]") + return f + + return int_range_checker + +if __name__ == "__main__": + + # Argument parser + parser = argparse.ArgumentParser(description="Passive wi-fi sniffer for the MAC Anonymization project. More info at README.md") + + # Options for sniffer + parser.add_argument("-L","--location",type=str,default=None,help="Defines the location of experiments.") + parser.add_argument("-i","--interface",type=str,default='wlan1',help="Chooses interface to listen to. Wireless interface must be on Monitor mode.") + parser.add_argument("-f","--filter",type=str,default='type mgt and (subtype probe-req or subtype probe-resp or subtype beacon)',help="Chooses filter to apply to packet capture. This is a string that follows the tcpdump conventions.") + parser.add_argument("-F","--folder",type=str,default=None,help="Folder to save traces in the remote nodes") + parser.add_argument('-v','--verbose',action='count',default=0,help='Increase verbosity level.') + parser.add_argument('-e','--hash-function',choices=availableHashFunctions,default='MD5',help="Chooses the desired function for hashing the MAC addresses (or no hash at all)") + parser.add_argument('-c','--channel',default=None,type=int,help="Set specific channel for capture (Default behaviour is not to change the current one)") + parser.add_argument("-o","--offline",default=None,type=Path,help="Reads from a pcap file instead of actually sniffing") + parser.add_argument('-m',"--memory",action="store_true",help="Writes pcap file into /dev/shm when capturing and moves from there to the default location when exiting. See README for more info") + parser.add_argument("-l","--logs",action="store_true",help="Saves the debug logs on a file") + parser.add_argument('-p','--hash-pattern',type=int_range(0,15),default=15,help="Integer defining bitmask that enables the hashing of MAC1, MAC2, MAC3 and SSID respectively. It's a binary value in the format 0b<MAC1><MAC2><MAC3><SSID> where each can be 1 or 0. Can be written as its respective integer as well.") + parser.add_argument('-t','--timeout',default=None,type=int,help="Time for capture packets on the interface specificied") + logger = logging.getLogger('sniffer') + + # Setting logs + # log instance + logger = logging.getLogger(name="ReplayFileCreator") + logger.setLevel(logging.DEBUG) + + # Parses arguments + args = parser.parse_args(sys.argv[1:]) + + # Saves debug log to file + if args.logs: + fileHandler = logging.FileHandler(f".sniffer.log") + fileHandler.setFormatter(logging.Formatter("%(asctime)s - %(funcName)s (%(lineno)s) - [%(levelname)s]: %(message)s")) + fileHandler.setLevel(logging.DEBUG) + logger.addHandler(fileHandler) + + # Prints with desired level + logHandler = logging.StreamHandler() + if args.verbose == 0: + logHandler.setLevel(logging.ERROR) + elif args.verbose == 1: + logHandler.setLevel(logging.WARNING) + elif args.verbose == 2: + logHandler.setLevel(logging.INFO) + else: + logHandler.setLevel(logging.DEBUG) + + logHandler.setFormatter(logging.Formatter("%(asctime)s - [%(levelname)s]: %(message)s")) + logger.addHandler(logHandler) + + # Arguments + verboseLevel = args.verbose + outputFile = f"{args.folder}/{dev_name}_{args.location}_tmsp{time.strftime('%Y%m%d-%H%M%S')}_lat{GPS_lat}_lon{GPS_lon}_ch{args.channel}.pcap" + sniffingFilter = args.filter # Defines a BPF filter to use with, defaults to only capture Probe requests, responses and beacons + listeningInterface = args.interface + HashFunction = hashFunctions[availableHashFunctions.index(args.hash_function)] + hashPattern = args.hash_pattern + time_out = args.timeout + #Sets verbosity level + if verboseLevel == 0: + logger.setLevel(logging.ERROR) + if verboseLevel == 1: + logger.setLevel(logging.WARNING) + if verboseLevel == 2: + logger.setLevel(logging.INFO) + if verboseLevel >= 3: + logger.setLevel(logging.DEBUG) + + logger.info(f"Current working interfaces: {(cwIfaces := scapy.get_working_ifaces())}") + # Checks if interface name is valid + if not listeningInterface in cwIfaces: + logger.critical(f"No interface named '{listeningInterface}' was found ") + exit(1) + + # Configures interface to be on monitor mode (no need if on offline mode) + if not args.offline: + try: + Utils.ConfigureInterface(listeningInterface) + if args.channel: + Utils.ChangeChannel(listeningInterface,args.channel) + except Exception as err: + logger.critical(f"Error when changing interface to monitor mode. ({err})") + exit(1) + + # Creates an PcapWriter object + # If --memory was selected, saves file into /dev/shm/ before saving it to CWD + if args.memory: + tempFileLocation = "/dev/shm/" + outputFile + ".tmp" + pcapWriter = scapy.PcapWriter(open(tempFileLocation,'wb'), append=True, sync=True) + else: + pcapWriter = scapy.PcapWriter(open(outputFile,'wb'), append=True, sync=True) + + # Starts sniffing process + logger.info(f"Started sniffing @ {listeningInterface} {datetime.now()} ") + + # If to read from file, reads with no filters, closes and exits the process + if args.offline: + sniffed = scapy.sniff(offline=args.offline.open(mode="rb"),prn=PacketHandler) + pcapWriter.close() + logger.info("Read from file") + exit(0) + + # If no offline option is given, proceeds to capture as normal + sniffed = scapy.sniff(filter=sniffingFilter,store=True,prn=PacketHandler,iface=listeningInterface,timeout=time_out) + logger.info(f"Written {len(sniffed)} captured packets") + + # Closes file + pcapWriter.close() + + # Copies content from shm to device + if args.memory: + with open(tempFileLocation,'rb') as filRead: + with open(outputFile,'wb') as filWrite: + filWrite.write(filRead.read()) + + logger.info("Success on exiting sniffer.") \ No newline at end of file