diff --git a/Figures/.gitkeep b/Figures/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Figures/ansible.png b/Figures/ansible.png new file mode 100644 index 0000000000000000000000000000000000000000..eb31a69b8cc39d0cb92d503a32ef7596198bc4ed Binary files /dev/null and b/Figures/ansible.png differ diff --git a/Figures/ansible_implementation.png b/Figures/ansible_implementation.png new file mode 100644 index 0000000000000000000000000000000000000000..084f432a8993ad16e8a9bacc3d1bf449c5a5d71e Binary files /dev/null and b/Figures/ansible_implementation.png differ diff --git a/Figures/architecture.png b/Figures/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..46ced84e82ea74069879669db44ab138497913b5 Binary files /dev/null and b/Figures/architecture.png differ diff --git a/Figures/gps_conn.png b/Figures/gps_conn.png new file mode 100644 index 0000000000000000000000000000000000000000..8605ac77c3b86bd6b9ecfaab1f5febcc29e20c8f Binary files /dev/null and b/Figures/gps_conn.png differ diff --git a/Figures/gps_module.png b/Figures/gps_module.png new file mode 100644 index 0000000000000000000000000000000000000000..91e75c02210a2387f5651db08fe3c0a29590a461 Binary files /dev/null and b/Figures/gps_module.png differ diff --git a/Figures/mitik_topology.png b/Figures/mitik_topology.png new file mode 100644 index 0000000000000000000000000000000000000000..fec1a1e3ba7f569fdcb2a522e780510fa6572e19 Binary files /dev/null and b/Figures/mitik_topology.png differ diff --git a/Figures/module_1.png b/Figures/module_1.png new file mode 100644 index 0000000000000000000000000000000000000000..882049814bbb23dcff1aedaf08b27dbdaab21363 Binary files /dev/null and b/Figures/module_1.png differ diff --git a/Figures/module_1_implementation.png b/Figures/module_1_implementation.png new file mode 100644 index 0000000000000000000000000000000000000000..cd9ec5180341b603ecf3456bbe3ef9b9899bf733 Binary files /dev/null and b/Figures/module_1_implementation.png differ diff --git a/Figures/module_2.png b/Figures/module_2.png new file mode 100644 index 0000000000000000000000000000000000000000..f1a49eb0ea3398631ceb664dfbb440f9265aa08e Binary files /dev/null and b/Figures/module_2.png differ diff --git a/Figures/module_3.png b/Figures/module_3.png new file mode 100644 index 0000000000000000000000000000000000000000..ae3081fbfe51dd6c50fb8521614dea9966b0d977 Binary files /dev/null and b/Figures/module_3.png differ diff --git a/Figures/randomized_MAC.png b/Figures/randomized_MAC.png new file mode 100644 index 0000000000000000000000000000000000000000..71833f72d881717f3732eee25480dc7f095013c0 Binary files /dev/null and b/Figures/randomized_MAC.png differ diff --git a/README.md b/README.md index 535e6219e95215f0f003431cfa64efdf7afc7398..0f41a3a11be00734e59db37a9a5c78706c3f33ce 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ Instructions are defined in Ansible's playbooks used to prepare all instructions ## Integration - - [ ] Number of sniffers by super-sniffer (5) - [x] Integrate Sniffers / Manager PC - [x] Integrate code sources from [1], [5] diff --git a/ansible/tasks/README.md b/ansible/tasks/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d38102ad1cc0ce9135b137f634e3b69d43f32093 --- /dev/null +++ b/ansible/tasks/README.md @@ -0,0 +1,100 @@ +# **Sniffers' configuration** + +Ansible[^1] involves a common set of concepts and tools for system automation. An inventory to define the hosts and groups of hosts participants that will be operated by the playbooks. Once the inventory has been defined, a set of playbooks have been programmed to perform tasks on the sniffers. The execution of the playbooks depends on the modules available in the Ansible platform. Finally, the system global interpreter is based on Python. + +[^1]: https://www.ansible.com/ + +The proposed topology is shown in Figure 1. One goal of using Ansible-based automation is to configure all sniffers simultaneously to guarantee that all nodes receive the same configuration, in addition to facilitating the manipulation of each device to be configured. + +<center> +<figure> + <img src="https://gitlab.inria.fr/fmorlano/mitik_management/-/raw/main/Figures/mitik_topology.png" width="70%" height="70%" alt="mitik_topology"/> + <figcaption>Figure 1. Topology of Mitik project.</figcaption> +</figure> +</center> + +The sniffers' deployment require two roles to be developed. The first environment has been created to perform the installation and configuration tasks required on the hardware and the O.S.; the second environment executes the tasks necessary to start the sniffer according to the required parameters, in addition to synchronizing the data with the sniffer manager (Mitik laptop) and the Mitik server. Figure 3 shows the scenario to be automated by the Ansible management tool. + +<center> +<figure> + <img src="https://gitlab.inria.fr/fmorlano/mitik_management/-/raw/main/Figures/ansible_implementation.png" width="70%" height="70%" alt="ansible_implementation"/> + <figcaption>Figure 3. Automating super-sniffers deployment.</figcaption> +</figure> +</center> + +## **Role 1. Hardware and software requirements** + +A modified version of Raspian has been provided by [1], as part of the experiments evaluating the performance of low-level libraries to capture network traffic in the sniffer. However, it is necessary to carry out additional configurations in the sniffer to add new functionalities on it. + +## **Sniffer manager** + +A sniffer manager is defined in a Mitik laptop (Macbook Pro). All the instructions executed in the sniffers are defined in the sniffer manager. To establish communication with the sniffers, an inventory with specific parameters is defined.Each sniffer is assigned a static IP. Besides that, sniffers are organized in groups (super-sniffers). _inventory_ shows the definition for each sniffer. Four groups (super-sniffers) are defined (ss1 to ss4), and each one contains five sniffers (sniffer#-ss#). + +In addition to the inventory, the sniffer manager sends all the instructions and configurations contained in the playbooks to be executed in each sniffer. + +### **Authentication** + +SSH key-based is used as authentication method between the sniffer manager and the sniffers. It is indispensable for the secure exchange information and data between the entities (sniffer manager and sniffers), besides of the execution of specific functions that requires SSH authentication. To enable the SSH Key-based authentication setup between the sniffer manager and the sniffers, _playbook_SSH_Keygen_ generates the public key of each sniffer, and also copy their SSH public keys to the sniffer manager. + +### **Sniffer Identification** + +Since each sniffer uses the O.S. from [1], _playbook_hostname_ and _playbook_hosts_ to redefine the hostname and local DNS of each sniffer based on the _inventory_ file and the static IP defined there. + +### **GPS Synchronization** + +Each step described in [README](https://gitlab.inria.fr/fmorlano/mitik_management/-/blob/main/docs/Installation%20manual.md) is executed in _playboork_GPS_sync_. + +### **Wireless Interfaces** + +To avoid randomness in the network interface names, the **Predictable network interface names** is disabled, and new udev rules are defined for assigning static interface names for each USB port. +``` +# +# +---------------+ +# | wlan1 | wlan3 | +# +-------+-------+ +# | wlan2 | wlan4 | +# +---------------+ (RPI USB ports distribution) +# +# | wlan0 | (onboard wifi) +# +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="sdio", ATTR{address}=="<MAC address onboard antenna>", KERNELS=="brcmfmac", NAME="wlan0" +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", KERNELS=="1-1.3", NAME="wlan1" +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", KERNELS=="1-1.4", NAME="wlan2" +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", KERNELS=="1-1.1", NAME="wlan3" +ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", KERNELS=="1-1.2", NAME="wlan4" +``` + +It is also defined a wireless network to connect each sniffer to the remote server through wlan0. These parameters are defined in _playbook_NIC_config_. It is worth noting that all wireless interfaces used to sniff must be connected in the same USB port for all sniffers. + +## **Role 2. Sniffer parameters** + +Unlike the single execution tasks of role 1, the tasks of role 2 can be executed multiple times, as long as they correspond to execution variables of the sniffer script. It is necessary to enter the online parameters to run the sniffer described in [1]. Host variables are defined in _playbook_scapy-sniffer_. Also, a timeout to stop sniffer execution has been added. By last, a job scheduling utility has been added to ensure that network time protocol set by Chrony is up to date for all sniffers. + +To start the sniffers, following parameters must be defined: + +- Hour to start the experiment, +- Minutes to start the experiment, +- Runtime duration in seconds, +- Wireless interface (by default, wlan1), +- Packet capture filter (by default, probe-req, probe-resp, beacon), +- Channel (by default, system), +- Hash funtion (by default, MD5), +- Hash pattern (by default, 15), +- Folder destination. + +The capture filename structure produced by sniffers will have the next format: packet_capture_{sniffer_i-super-sniffer_id}-ts-{timestamp}-ch{channel}-gps{lat/lon}.pcap + +On the other hand, each single capture file from the sniffers is sent via SSH connection to the Mitik laptop or Mitik server to be analyzed in the **Trace handling engine** and the **Trace production engine**. The _playbook_data_transfer_ contains the instructions to send the data to the Mitik laptop or Mitik server. + +# ------------------ **TODO** -------------------- + +Tasks: + +- [x] UPLOAD FUNCTIONAL PLAYBOOKS OF THE FIRST TESTBED +- [x] SPECIFY FEATURES OF EACH PLAYBOOK +- [x] ORGANIZE FEATURES IN PLAYBOOKS BY TASK TYPE +- [ ] CREATE MAIN.YML TO EXECUTE ALL THE PLAYBOOKS + +## **References** + +[1] Fernando Dias de Mello Silva, Abhishek Kumar Mishra, Aline Carneiro Viana, Nadjib Achir, Anne Fladenmuller, and Lu Ìıs Henrique M. K. Costa. Performance analysis of a privacy-preserving frame sniffer on a raspberry pi. In 6th Cyber Security in Networking Conference (CSNet), pages 1–7, October 2022. diff --git a/ansible/tasks/playbook_GPS_sync b/ansible/tasks/playbook_GPS_sync new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ansible/tasks/playbook_GPS_sync.yml b/ansible/tasks/playbook_GPS_sync.yml new file mode 100644 index 0000000000000000000000000000000000000000..6aca59afc8693fa83f4e4ddf3132f52b7a5663d4 --- /dev/null +++ b/ansible/tasks/playbook_GPS_sync.yml @@ -0,0 +1,65 @@ +- hosts: sniffers + user: gta + become: yes + become_user: root + tasks: + + - name: Timezone config + command: sudo timedatectl set-timezone Europe/Paris + + - name: Raspi-config serial_hw + command: sudo raspi-config nonint do_serial 2 + + - name: gpsd installation + command: sudo apt install gpsd -y + + - name: gpsd-clients installation + command: sudo apt install gpsd-clients -y + + - name: pps-tools installation + command: sudo apt install pps-tools -y + + - name: systemctl enable gpsd + command: sudo systemctl enable gpsd.socket + + - name: Copy a new config gpsd device functionality + blockinfile: | + dest=/boot/config.txt + content="dtoverlay=pps-gpio,gpiopin=18 + enable_uart=1 + init_uart_baud=9600" + + - name: Copy a new pps GPIO device in modules + blockinfile: | + dest=/etc/modules + content="pps-gpio" + + - name: gpsd port pointer + command: sudo gpsd /dev/ttyS0 -F /var/run/gpsd.sock + + - name: Copy a new config gpsd device functionality + copy: + src: /etc/default/gpsd + dest: /etc/default/gpsd + + - name: Reconfigure gpsd + command: sudo dpkg-reconfigure gpsd + + - name: Remove IPv6 bind + copy: + src: /home/pi/ansible/gpsd.socket + dest: /lib/systemd/system/gpsd.socket + + - name: ntp uninstall + command: sudo apt remove ntp -y + + - name: chrony installation + command: sudo apt install chrony -y + + - name: chrony config file GPS time synchronization + copy: + src: /etc/chrony/chrony.conf + dest: /etc/chrony/chrony.conf + + - name: systemctl enable chrony + command: sudo systemctl restart chrony diff --git a/ansible/tasks/playbook_NIC_config.yml b/ansible/tasks/playbook_NIC_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb885323b3d2f26498d50b41ed13de90735d827f --- /dev/null +++ b/ansible/tasks/playbook_NIC_config.yml @@ -0,0 +1,13 @@ +- hosts: sniffers + user: gta + become: yes + become_user: root + tasks: + + - name: Switch off predictable mechanism stuff + command: ls -nfs /dev/null /etc/systemd/network/99-default.link + + - name: Copy new rules to identify interfaces based on USB positions + copy: + src: /etc/udev/rules.d/72-wlan-geo-dependent.rules + dest: /etc/udev/rules.d/72-wlan-geo-dependent.rules diff --git a/ansible/tasks/playbook_SSH_keygen.yml b/ansible/tasks/playbook_SSH_keygen.yml new file mode 100644 index 0000000000000000000000000000000000000000..c369424291b146a8c38bb28f7d6317c65cbbd9c9 --- /dev/null +++ b/ansible/tasks/playbook_SSH_keygen.yml @@ -0,0 +1,25 @@ +- name: Exchange Keys between servers + hosts: sniffers + tasks: + - name: SSH KeyGen command + tags: run + shell: > + ssh-keygen -q -b 2048 -t rsa -N "" -C "creating SSH" -f ~/.ssh/id_rsa + creates="~/.ssh/id_rsa" + + - name: Fetch the keyfile from the node to master + tags: run + fetch: + src: "~/.ssh/id_rsa.pub" + dest: "buffer/{{ansible_hostname}}-id_rsa.pub" + flat: yes + + - name: Copy the key add to authorized_keys using Ansible module + tags: runcd + authorized_key: + user: gta + state: present + key: "{{ lookup('file','buffer/{{item}}-id_rsa.pub')}}" + when: "item != ansible_hostname" + with_items: + - "{{ groups['sniffers'] }}" diff --git a/ansible/tasks/playbook_data_transfer b/ansible/tasks/playbook_data_transfer new file mode 100644 index 0000000000000000000000000000000000000000..e8d04303018d7105ec113e1144a61c6ac598d22e --- /dev/null +++ b/ansible/tasks/playbook_data_transfer @@ -0,0 +1,25 @@ +- hosts: sniffers + user: gta + #become: yes + #become_user: root + + vars: + src_file: "/home/gta/sniffers/scapy-sniffer/capture-*.pcap" + dest_file: "/Users/fmolano/ansible/files/" + cap_file: capture-* + + tasks: + + - name: find files to copy + find: + paths: "/home/gta/sniffers/scapy-sniffer/" + recurse: no + patterns: "*.pcap" + register: files_to_copy + + - name: Copy files + fetch: + src: "{{ item.path }}" + dest: /Users/fmolano/ansible/files/{{ ansible_hostname }}_{{ ansible_date_time.date }}/ + flat: yes + with_items: "{{ files_to_copy.files }}" \ No newline at end of file diff --git a/ansible/tasks/playbook_hostname.yml b/ansible/tasks/playbook_hostname.yml new file mode 100644 index 0000000000000000000000000000000000000000..c63110be3cdc335e46b397e3f885982e37cb9ad6 --- /dev/null +++ b/ansible/tasks/playbook_hostname.yml @@ -0,0 +1,4 @@ +- hosts: all + tasks: + - name: set system hostname + command: sudo hostnamectl set-hostname {{ inventory_hostname }} diff --git a/ansible/tasks/playbook_hosts.yml b/ansible/tasks/playbook_hosts.yml new file mode 100644 index 0000000000000000000000000000000000000000..8eacb9e5095b6a1d43b0ac48b2a61a552daf6939 --- /dev/null +++ b/ansible/tasks/playbook_hosts.yml @@ -0,0 +1,18 @@ + - name: host file update - Local DNS setup across all the servers + hosts: sniffers + gather_facts: yes + tasks: + + - name: Update the /etc/hosts file with node name + tags: etchostsupdate + become: yes + become_user: root + lineinfile: + 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']}}" + state: present + register: etchostsupdate + when: + ansible_hostname != "item" or ansible_hostname == "item" + with_items: "{{groups['sniffers']}}" diff --git a/ansible/tasks/playbook_scapy-sniffer_GPS.yml b/ansible/tasks/playbook_scapy-sniffer_GPS.yml new file mode 100644 index 0000000000000000000000000000000000000000..c237893a9f911ea838053a6ea7d39dccc3216d73 --- /dev/null +++ b/ansible/tasks/playbook_scapy-sniffer_GPS.yml @@ -0,0 +1,69 @@ +- hosts: sniffers + user: gta + #become: yes + #become_user: root + + vars_prompt: + + - name: _file + prompt: Enter the folder name to save traces in the sniffer + private: no + + - name: _location + prompt: Enter the location of the experiments + private: no + + - name: _hour + prompt: Enter an hour to start the experiment + private: no + + - name: _minute + prompt: Enter minutes to start the experiment + private: no + + - name: _timeout + prompt: Please specify the runtime duration in sec + private: no + + - name: _interface + prompt: Please specify the interface (e.g. wlan0). For default just press enter + default: 'wlan1' + private: no + + - name: _filter + prompt: Please choose the filter to apply to packet capture. For default just press enter + default: 'type mgt and (subtype probe-req or subtype probe-resp or subtype beacon)' + private: no + + - name: _channel + prompt: Please specify the channel (integer between 1-11, default=system). For default just press enter + default: 1 + private: no + + - name: _hash_function + prompt: Please specify the hash function (None, MD5, SHA256). For default just press enter + default: 'MD5' + private: no + + - name: _hash_pattern + prompt: Please specify the bitmask for hashing of MAC1, MAC2, MAC3 and SSID (integer between 0-15, default=15). For default just press enter + default: 15 + private: no + + tasks: + + - name: Create Folder + file: + path: "{{ _file }}" + owner: gta + group: gta + #mode: 0755 + state: directory + + - name: start scapy-sniffer + + cron: + name: "ansible_scapy-sniffer {{ _hour }} {{ _minute }} {{ _channel }}" + minute: "{{ _minute }}" + hour: "{{ _hour }}" + 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/docs/Installation manual.md b/docs/Installation manual.md new file mode 100644 index 0000000000000000000000000000000000000000..81496a982b9499612192ee078ae10487ab2b1c05 --- /dev/null +++ b/docs/Installation manual.md @@ -0,0 +1,100 @@ +# **Installing the sniffer manager** + +The sniffer manager consists of several configuration components. Each tool used will be discussed below to ensure the succesfull tool implementation. + +## **Hardware** + +### **Raspberry Pi** +The sniffers use Raspbian Bullseye simplified version installed on the Raspberry Pi 4B. Raspberry PI allows the use of low-cost controllers to enable other functionalities such as GPS. + +### **Wireless interfaces** +Each Raspberry Pi is equipped with external TP-Link TL-WN722N wireless interfaces which make available the monitor mode, required for the analysis of captured network traffic in the sniffer. + +### **GPS module** +Each Raspberry Pi is equipped with an [u-blox NEO 6M-0-001](https://content.u-blox.com/sites/default/files/products/documents/NEO-6_DataSheet_%28GPS.G6-HW-09005%29.pdf). This module has one timepulse PPS module, in addition to the stand-alone GPS receiver. + +<center> +<figure> + <img src="https://gitlab.inria.fr/fmorlano/mitik_management/-/raw/main/Figures/gps_module.png" alt="gps_module"/> + <figcaption>Figure 1. GPS module.</figcaption> +</figure> +</center> + +## **Software** + +### **Ansible** + +The sniffer manager uses Ansible to configure all the sniffer parameters. This can be used remotely or locally. So, we use our own made Ansible-based tool to configure the sniffers with all the required parameters. Ansible is compatible with Linux distributions Mac OS and Windows. Currently, we use a Macbook as manager for our development. + +``` +# apt install ansible -y +``` + +To establish communication with the sniffers, an inventory is defined. Each sniffer is configured with a static IP. Besides that, the sniffers are organized in groups (super-sniffers). + +### **Time synchronization and GPS configuration** + +We consider to use time synchronization via GPS PPS (Pulse Per Second) signal. + +Install **gpsd** for GPS decoding of both time and position; **pps-tools** to verify PPS signals from the GPS; and **chrony** to handle PPS signals. +``` +# apt install gpsd gpsd-clients pps-tools chrony +``` + +Install **pynmea2** for interpretation of messages provided by the GPS based on the NMEA 0183 [1]. NMEA format has different properties, depending on its sentence type and the properties in the message data. For this application, $GPGGA format is udes to extract latitude, longitude and datetime. +``` +# pip3 install pynmea2 +``` + +PPS configuration requires intruction definitions in _/boot/config.txt_. For NMEA data from the serial communication, it is necessary to enable UART communication and set the baud rate. + +``` +# bash -c "echo 'dtoverlay=pps-gpio,gpiopin=18' >> /boot/config.txt" +# bash -c "echo 'enable_uart=1' >> /boot/config.txt" +# bash -c "echo 'init_uart_baud=9600' >> /boot/config.txt" +``` + +It is also necessary to add PPS in _/etc/modules_. +``` +# bash -c "echo 'pps-gpio' >> /etc/modules +``` + +#### **Pin connections** + +| GPS | Raspberry Pi | +| :---: | :---: | +| PPS | Pin 12 (GPIO 18)| +| VCC | Pin 4 | +| GND | Pin 6 | +| RX | Pin 8 | +| TX | Pin 10 | + +<center> +<figure> + <img src="https://gitlab.inria.fr/fmorlano/mitik_management/-/raw/main/Figures/gps_conn.png" alt="gps_connection"/> + <figcaption>Figure 2. Wire up GPS module to Raspberry Pi.</figcaption> +</figure> +</center> + +It is needed to make the UART serial port enabled: + +``` +# raspi-config nonint do_serial 2 +``` +Configuration of **gpsd** in _/etc/default/gpsd_ to connect before polling whatever GPS must be associated with it. This is used to provide reference clock information to ntpd or chronyd. Moreover, the device argument should be defined as /dev/ttyS0 to /dev/pps0 to monitor the serial data for PPS. + +``` +# Default settings for the gpsd init script and the hotplug wrapper. +START_DAEMON="true" +USBAUTO="true" +DEVICES="/dev/ttyS0 /dev/pps0" +GPSD_OPTIONS="-n" +GPSD_SOCKET="/var/run/gpsd.sock" +``` + +Configuration of PPS as time reference for **chrony** server. + +``` +# bash -c "echo 'refclock SHM 0 delay 0.200 refid NMEA' >> /etc/chrony/chrony.conf" +# bash -c "echo 'refclock PPS /dev/pps0 refid PPS' >> /etc/chrony/chrony.conf" +``` diff --git a/utils/Utils.py b/utils/Utils.py new file mode 100644 index 0000000000000000000000000000000000000000..3f6a1671c7d2b1908e46c2d8336563ee1b1e62b0 --- /dev/null +++ b/utils/Utils.py @@ -0,0 +1,31 @@ +import subprocess + +captureFilter = 'type mgt and (subtype probe-req or subtype probe-resp or subtype beacon)' +# +# +# +# +def ConfigureInterface(interface : str): + # Setting desired interface to monitor mode + p = subprocess.Popen(['/sbin/ifconfig',interface,'down'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) + p.wait() + if p.poll(): + stdout, stderr = p.communicate() + raise Exception(f"ERROR: Failed to set '{interface}' down: {stderr}") + + p = subprocess.Popen(['/sbin/iwconfig',interface,'mode','monitor'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) + p.wait() + if p.poll(): + stdout, stderr = p.communicate() + raise Exception(f"ERROR: Failed to change '{interface}' to Monitor mode: {stderr}") + + p = subprocess.Popen(['/sbin/ifconfig',interface,'up'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) + p.wait() + if p.poll(): + stdout, stderr = p.communicate() + raise Exception(f"ERROR: Failed to set '{interface}' up: {stderr}") + +def ChangeChannel(interface:str, channel:int): + p = subprocess.run(['/sbin/iwconfig',interface,'channel',f"{channel}"],capture_output=True) + if p.returncode: + raise Exception(f"Failed to set Channel: '{p.stderr}'") \ 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