Repository: tenable/pi_sniffer
Branch: master
Commit: 0d19e5ea340e
Files: 39
Total size: 152.5 KB
Directory structure:
gitextract_gsji9l86/
├── .gitignore
├── LICENSE
├── README.md
├── configs/
│ ├── kismet.conf
│ ├── ntp.conf
│ └── rc.local
├── image_gen/
│ └── create_image.sh
├── pi_sniffer/
│ ├── CMakeLists.txt
│ ├── pi_sniffer.conf
│ └── src/
│ ├── ap.cpp
│ ├── ap.hpp
│ ├── client.cpp
│ ├── client.hpp
│ ├── configuration.cpp
│ ├── configuration.hpp
│ ├── input/
│ │ ├── kismet_drone.cpp
│ │ ├── kismet_drone.hpp
│ │ ├── pcap.cpp
│ │ └── pcap.hpp
│ ├── main.cpp
│ ├── packet.cpp
│ ├── packet.hpp
│ ├── probed_network.cpp
│ ├── probed_network.hpp
│ ├── protocols/
│ │ ├── eapol11.cpp
│ │ ├── eapol11.hpp
│ │ ├── ieee80211.cpp
│ │ ├── ieee80211.hpp
│ │ ├── llcsnap.cpp
│ │ └── llcsnap.hpp
│ ├── stats.cpp
│ ├── stats.hpp
│ └── util/
│ ├── convert.cpp
│ ├── convert.hpp
│ ├── kml_maker.cpp
│ ├── kml_maker.hpp
│ ├── pcap_output.cpp
│ └── pcap_output.hpp
└── ui/
└── display_handler.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.img
# Logs and databases #
######################
*.log
*.sql
*.sqlite
*.pcap
*.csv
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
================================================
FILE: LICENSE
================================================
This file is part of Pi Sniffer.
Pi Sniffer 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.
Pi Sniffer 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 Foobar. If not, see <https://www.gnu.org/licenses/>.
================================================
FILE: README.md
================================================
# Pi Sniffer
Pi Sniffer is a Wi-Fi sniffer built on the Raspberry Pi Zero W. While there are many excellent sniffing platforms out there, Pi Sniffer is unique for it's small size, real time display of captured data, and handling of user input.
<image src="https://user-images.githubusercontent.com/787916/75169212-291e0c00-56f6-11ea-8ae9-13e4a2762276.jpg" height="66%" width="66%">
## Current Release Image
You can download an an RPI image of this project from the "Releases" page. If you don't trust that, you can generate your own release by using the image_gen/create_image.sh script.
## Project Goals
The goal of this project was to create a Wi-Fi sniffer that I could carry around in my pocket, easily view real time status, decrypt packets on the fly, and change antenna channels as needed. Also, I wanted this project to be cheap (less than $100) and require no soldering.
## Hardware
The project was conceived with the goal to avoid any type of soldering. While Pi Sniffer does require the GPIO header on the Raspberry Pi Zero W, you can buy that pre-soldered. So I'm gonna claim no soldering required.
The base install requires:
* [Raspberry Pi Zero WH](https://www.adafruit.com/product/3708)
* [Adafruit 128x64 OLED Bonnet](https://www.adafruit.com/product/3531)
* A power source. I suggest one of the following:
* [Anker PowerCore 5000](https://www.amazon.com/dp/B01CU1EC6Y)
* [Anker E1 Astro](https://www.amazon.com/Anker-bar-Sized-Portable-High-Speed-Technology/dp/B00P7N0320)
* [UPS-Lite](https://www.tindie.com/products/rachel/ups-lite-for-raspberry-pi-zero/)
* Any SD card 8GB or larger
Additionally, you can configure the device with any of the following add-ons (and still reasonably be called pocket sized):
* [Secondary antenna by CanaKit](https://www.amazon.com/CanaKit-Raspberry-Wireless-Adapter-Dongle/dp/B00GFAN498)
* [Ublox-7 GPS](https://www.amazon.com/WINGONEER%C2%AE%C2%AE-Antenna-VK-172-Receiver-Windows/dp/B07F6TJG9L)
* [MicroUSB to USB adapters](https://www.amazon.com/Ksmile%C2%AE-Female-Adapter-SamSung-tablets/dp/B01C6032G0)
* [USB MiniHub](https://www.adafruit.com/product/2991)
## Software
Download the release image and flash it to an SD card. Stick the SD card into your RPI Zero WH and you should be good to go! By default, SSH should be enabled. Use the default pi:raspberry credentials. The device's hostname is pisniffer so something along the following lines should get you in:
```sh
ssh pi@pisniffer.local
```
## Controls
Pi Sniffer isn't unique just due to it's size but it also offers controls. The user can start and stop sniffing. Change channels. Deauth clients. And more. Here are some images showing how to use the controls.
### Start, Stop, and Shutdown
To start sniffing hit the #6 button. To stop sniffing hit the #5 button. To shutdown the device hold #5 and #6.

### Channel Hoppping
To change to a specific channel, rotate to the antenna screen and hit #6. This will cycle you through the available channels plus hopping.

### Deauth Attack
To deauth a client, find them in the client view and hit #6.

### Lock display
Sometimes it's beneficial to lock the screen and controls. To do so, rotate to the lock screen and hit #6. To unlock you need to hit #5 and push up on the joystick at the same time.

## Issues and Pull Requests
Issues and pull requests are welcome. I only ask that you provide enough information to recreate the issue or information about why the pull request should be accepted.
================================================
FILE: configs/kismet.conf
================================================
# Version of Kismet config
version=2009-newcore
# Do we process the contents of data frames? If this is enabled, data
# frames will be truncated to the headers only immediately after frame type
# detection. This will disable IP detection, etc, however it is likely
# safer (and definitely more polite) if monitoring networks you do not own.
hidedata=true
# Do we allow plugins to be used? This will load plugins from the system
# and user plugin directiories when set to true (See the README for the default
# plugin locations).
allowplugins=false
# See the README for full information on the new source format
# ncsource=interface:options
# for example:
ncsource=wlan0:uuid=00000000-0000-0000-0000-000000000001
ncsource=wlan1:uuid=00000000-0000-0000-0000-000000000002
# ncsource=wifi0:type=madwifi
# ncsource=wlan0:name=intel,hop=false,channel=11
# Control which channels we like to spend more time on. By default, the list
# of channels is pulled from the driver automatically. By setting preferred channels,
# if they are present in the channel list, they'll be set with a timing delay so that
# more time is spent on them. Since 1, 6, 11 are the common default channels, it makes
# sense to spend more time monitoring them.
# For finer control, see further down in the config for the channellist= directives.
preferredchannels=1,6,11
# How many channels per second do we hop? (1-10)
channelvelocity=3
# Default channel lists
# These channel lists MUST BE PRESENT for Kismet to work properly. While it is
# possible to change these, it is not recommended. These are used when the supported
# channel list can not be found for the source; to force using these instead of
# the detected supported channels, override with channellist= in the source defintion
#
# IN GENERAL, if you think you want to modify these, what you REALLY want to do is
# copy them and use channellist= in the packet source.
channellist=IEEE80211b:1:3,6:3,11:3,2,7,3,8,4,9,5,10
channellist=IEEE80211a:36,40,44,48,52,56,60,64,149,153,157,161,165
channellist=IEEE80211ab:1:3,6:3,11:3,2,7,3,8,4,9,5,10,36,40,44,48,52,56,60,64,149,153,157,161,165
# Client/server listen config
listen=tcp://127.0.0.1:2501
# People allowed to connect, comma seperated IP addresses or network/mask
# blocks. Netmasks can be expressed as dotted quad (/255.255.255.0) or as
# numbers (/24)
allowedhosts=127.0.0.1
# Maximum number of concurrent GUI's
maxclients=1
# Maximum backlog before we start throwing out or killing clients. The
# bigger this number, the more memory and the more power it will use.
maxbacklog=50
# Server + Drone config options. To have a Kismet server export live packets
# as if it were a drone, uncomment these.
dronelisten=tcp://127.0.0.1:3501
droneallowedhosts=127.0.0.1
dronemaxclients=1
droneringlen=65535
# Do we have a GPS?
gps=true
# Do we use a locally serial attached GPS, or use a gpsd server, or
# use a fixed virtual gps?
# (Pick only one)
gpstype=gpsd
# Host:port that GPSD is running on. This can be localhost OR remote!
gpshost=localhost:2947
# gpstype=serial
# What serial device do we look for the GPS on?
# gpsdevice=/dev/rfcomm0
# gpstype=virtual
# gpsposition=100,-50
# gpsaltitude=1234
# Do we lock the mode? This overrides coordinates of lock "0", which will
# generate some bad information until you get a GPS lock, but it will
# fix problems with GPS units with broken NMEA that report lock 0
gpsmodelock=false
# Do we try to reconnect if we lose our link to the GPS, or do we just
# let it die and be disabled?
gpsreconnect=true
# Do we export packets over tun/tap virtual interfaces?
tuntap_export=false
tuntap_device=kistap0
# Is transmission of the keys to the client allowed? This may be a security
# risk for some. If you disable this, you will not be able to query keys from
# a client.
allowkeytransmit=true
# How often (in seconds) do we write all our data files (0 to disable)
writeinterval=0
# Do we use sound?
# Not to be confused with GUI sound parameter, this controls wether or not the
# server itself will play sound. Primarily for headless or automated systems.
enablesound=false
# Format of the pcap dump (PPI or 80211)
pcapdumpformat=ppi
# Default log title
logdefault=Kismet
# logtemplate - Filename logging template.
# This is, at first glance, really nasty and ugly, but you'll hardly ever
# have to touch it so don't complain too much.
#
# %p is replaced by the logging prefix + '/'
# %n is replaced by the logging instance name
# %d is replaced by the starting date as Mon-DD-YYYY
# %D is replaced by the current date as YYYYMMDD
# %t is replaced by the starting time as HH-MM-SS
# %i is replaced by the increment log in the case of multiple logs
# %l is replaced by the log type (pcapdump, strings, etc)
# %h is replaced by the home directory
logtemplate=%p%n-%D-%t-%i.%l
# Where state info, etc, is stored. You shouldnt ever need to change this.
# This is a directory.
configdir=/tmp/.kismet/
================================================
FILE: configs/ntp.conf
================================================
# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
driftfile /var/lib/ntp/ntp.drift
# Leap seconds definition provided by tzdata
leapfile /usr/share/zoneinfo/leap-seconds.list
# Enable this if you want statistics to be logged.
#statsdir /var/log/ntpstats/
statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable
# You do need to talk to an NTP server or two (or three).
#server ntp.your-provider.example
# pool.ntp.org maps to about 1000 low-stratum NTP servers. Your server will
# pick a different set every time it starts up. Please consider joining the
# pool: <http://www.pool.ntp.org/join.html>
pool 0.debian.pool.ntp.org iburst
pool 1.debian.pool.ntp.org iburst
pool 2.debian.pool.ntp.org iburst
pool 3.debian.pool.ntp.org iburst
# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
# details. The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
# might also be helpful.
#
# Note that "restrict" applies to both servers and clients, so a configuration
# that might be intended to block requests from certain clients could also end
# up blocking replies from your own upstream servers.
# By default, exchange time with everybody, but don't allow configuration.
restrict -4 default kod notrap nomodify nopeer noquery limited
restrict -6 default kod notrap nomodify nopeer noquery limited
# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
restrict ::1
# Needed for adding pool entries
restrict source notrap nomodify noquery
# Clients from this (example!) subnet have unlimited access, but only if
# cryptographically authenticated.
#restrict 192.168.123.0 mask 255.255.255.0 notrust
# If you want to provide time to your local subnet, change the next line.
# (Again, the address is an example only.)
#broadcast 192.168.123.255
# If you want to listen to time broadcasts on your local subnet, de-comment the
# next lines. Please do this only if you trust everybody on the network!
#disable auth
#broadcastclient
# GPS Serial data reference
server 127.127.28.0 minpoll 4 maxpoll 4
fudge 127.127.28.0 time1 0.0 refid GPS
# GPS PPS reference
server 127.127.28.1 minpoll 4 maxpoll 4 prefer
fudge 127.127.28.1 refid PPS
================================================
FILE: configs/rc.local
================================================
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
(python3 /home/pi/display_handler.py > /dev/null)&
exit 0
================================================
FILE: image_gen/create_image.sh
================================================
#!/usr/bin/env bash
# based on: https://wiki.debian.org/RaspberryPi/qemu-user-static
# and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/
#
# and pwnagotchi's old create_sibling script:
# https://raw.githubusercontent.com/evilsocket/pwnagotchi/55bac8a8b913c158132953d8186d278621df032a/scripts/create_sibling.sh
set -eu
if [[ "$EUID" -ne 0 ]]; then
echo "Run this script as root!"
exit 1
fi
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
TMP_DIR="${REPO_DIR}/tmp"
MNT_DIR="${TMP_DIR}/mnt"
THIS_DIR=$(pwd)
HOST_NAME="pisniffer"
OUTPUT_NAME="pi_sniffer.img"
IMAGE_SIZE="7"
function check_dependencies() {
if [ -f /etc/debian_version ];
then
echo "[+] Checking Debian dependencies"
for REQ in "${DEBREQUIREMENTS[@]}"; do
if ! dpkg -s "$REQ" >/dev/null 2>&1; then
echo "Dependency check failed for ${REQ}; use 'apt install ${REQ}' to install"
exit 1
fi
done
fi
echo "[+] Checking dependencies"
for REQ in "${REQUIREMENTS[@]}"; do
if ! type "$REQ" >/dev/null 2>&1; then
echo "Dependency check failed for ${REQ}"
exit 1
fi
done
if ! test -e /usr/bin/qemu-arm-static; then
echo "[-] You need the package \"qemu-user-static\" for this to work."
exit 1
fi
if ! systemctl is-active systemd-binfmt.service >/dev/null 2>&1; then
mkdir -p "/lib/binfmt.d"
echo ':qemu-arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:F' > /lib/binfmt.d/qemu-arm-static.conf
systemctl restart systemd-binfmt.service
fi
}
function get_raspbian() {
URL="https://downloads.raspberrypi.org/raspbian_lite_latest"
echo "[+] Downloading raspbian lite latest to raspbian.zip"
mkdir -p "${TMP_DIR}"
wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "$URL"
echo "[+] Unpacking raspbian.zip to raspbian.img"
gunzip -c "${TMP_DIR}/raspbian.zip" > "${TMP_DIR}/raspbian.img"
}
function setup_raspbian(){
# Note that we 'extend' the raspbian.img
echo "[+] Resizing full image to ${IMAGE_SIZE}G"
# Full disk-space using image (appends to raspbian image)
dd if=/dev/zero bs=1G count="${IMAGE_SIZE}" >> "${TMP_DIR}/raspbian.img"
truncate --size="${IMAGE_SIZE}"G "${TMP_DIR}/raspbian.img"
echo "[+] Setup loop device"
mkdir -p "${MNT_DIR}"
LOOP_PATH="$(losetup --find --partscan --show "${TMP_DIR}/raspbian.img")"
PART2_START="$(parted -s "$LOOP_PATH" -- print | awk '$1==2{ print $2 }')"
parted -s "$LOOP_PATH" rm 2
parted -s "$LOOP_PATH" mkpart primary "$PART2_START" 100%
echo "[+] Check FS"
e2fsck -y -f "${LOOP_PATH}p2"
echo "[+] Resize FS"
resize2fs "${LOOP_PATH}p2"
echo "[+] Device is ${LOOP_PATH}"
echo "[+] Unmount if already mounted with other img"
mountpoint -q "${MNT_DIR}" && umount -R "${MNT_DIR}"
echo "[+] Mount /"
mount -o rw "${LOOP_PATH}p2" "${MNT_DIR}"
echo "[+] Mount /boot"
mount -o rw "${LOOP_PATH}p1" "${MNT_DIR}/boot"
mount --bind /dev "${MNT_DIR}/dev/"
mount --bind /sys "${MNT_DIR}/sys/"
mount --bind /proc "${MNT_DIR}/proc/"
mount --bind /dev/pts "${MNT_DIR}/dev/pts"
cp /usr/bin/qemu-arm-static "${MNT_DIR}/usr/bin"
cp /etc/resolv.conf "${MNT_DIR}/etc/resolv.conf"
}
function provision_raspbian() {
# copy in pi sniffer
cd ${MNT_DIR}/home/pi/
cp -r ${REPO_DIR}/pi_sniffer .
cp ${REPO_DIR}/ui/* .
cp ${REPO_DIR}/configs/rc.local ../../etc/
chmod +x /etc/rc.local
cp ${REPO_DIR}/configs/kismet.conf .
cp ${REPO_DIR}/configs/ntp.conf ../../etc/
cd ${MNT_DIR}
sed -i'' 's/^\([^#]\)/#\1/g' etc/ld.so.preload # add comments
echo "[+] Run chroot commands"
LANG=C LC_ALL=C LC_CTYPE=C chroot . bin/bash -x <<EOF
set -eu
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
uname -a
apt -y update
apt -y upgrade
apt -y install git build-essential python3-pip tcpdump
apt -y install libpcap-dev cmake gpsd gpsd-clients
apt -y install aircrack-ng kismet libtins-dev libpugixml-dev
apt -y install libssl-dev python3-pil python-smbus i2c-tools python-gi
apt -y install libboost-all-dev
apt -y install ntp
# setup dphys-swapfile
echo "CONF_SWAPSIZE=1024" >/etc/dphys-swapfile
systemctl enable dphys-swapfile.service
# compile pi sniffer
cd home/pi/pi_sniffer
mkdir build
cd build
cmake ..
make
cd /
# configure pwnagotchi
echo -e "$HOST_NAME" > /etc/hostname
sed -i "s@^127\.0\.0\.1 .*@127.0.0.1 localhost "$HOST_NAME" "$HOST_NAME".local@g" /etc/hosts
# interface dependencies
pip3 install adafruit-circuitpython-ssd1306 spidev RPI.GPIO adafruit-blinka
echo "dtparam=i2c_arm=on" >> boot/config.txt
echo "dtparam=spi=on" >> boot/config.txt
echo "i2c-dev" >> etc/modules
echo "hi" >> boot/ssh
# Re4son-Kernel
echo "deb http://http.re4son-kernel.com/re4son/ kali-pi main" > /etc/apt/sources.list.d/re4son.list
wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add -
apt update
apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin
# Fix PARTUUID
PUUID_ROOT="\$(blkid "\$(df / --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')"
PUUID_BOOT="\$(blkid "\$(df /boot --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')"
# sed regex info: search for line containing / followed by whitespace or /boot (second sed)
# in this line, search for PARTUUID= followed by letters, numbers or "-"
# replace that match with the new PARTUUID
sed -i "/\/[ ]\+/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_ROOT/g" /etc/fstab
sed -i "/\/boot/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_BOOT/g" /etc/fstab
sed -i "s/root=[^ ]\+/root=PARTUUID=\${PUUID_ROOT}/g" /boot/cmdline.txt
# delete keys
find /etc/ssh/ -name "ssh_host_*key*" -delete
# slows down boot
systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service
# unecessary services
systemctl disable triggerhappy bluetooth wpa_supplicant
EOF
sed -i'' 's/^#//g' etc/ld.so.preload
cd "${REPO_DIR}"
umount -R "${MNT_DIR}"
losetup -D "$(losetup -l | awk '/raspbian\.img/{print $1}')"
mv "${TMP_DIR}/raspbian.img" "${OUTPUT_NAME}"
}
check_dependencies
get_raspbian "latest"
setup_raspbian
provision_raspbian
echo -e "[+] Done."
================================================
FILE: pi_sniffer/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 2.8)
# Options
option(debug "Build with debug flags." Off)
set(PROJECT_NAME pi_sniffer)
project(${PROJECT_NAME})
# compiler flags
if (debug)
set(CMAKE_CXX_FLAGS "-g -std=c++0x -Wall -Wextra -Weffc++ -Wshadow -pedantic -Wcast-align -Wcast-qual -Woverloaded-virtual -Wstrict-null-sentinel -Wswitch-default -Winit-self -Wlogical-op -Wno-deprecated-declarations")
else()
set(CMAKE_CXX_FLAGS "-O2 -std=c++0x -Wall -Wextra -Wno-deprecated-declarations")
endif()
#package locations
find_package(Boost 1.67 COMPONENTS system thread program_options filesystem atomic REQUIRED)
find_package(OpenSSL REQUIRED)
# includes
include_directories(SYSTEM ${Boost_INCLUDE_DIR})
include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR})
include_directories(SYSTEM "src/")
# compilation units
add_executable(${PROJECT_NAME}
src/main.cpp
src/ap.cpp
src/client.cpp
src/probed_network.cpp
src/configuration.cpp
src/packet.cpp
src/stats.cpp
src/input/kismet_drone.cpp
src/input/pcap.cpp
src/protocols/ieee80211.cpp
src/protocols/llcsnap.cpp
src/protocols/eapol11.cpp
src/util/kml_maker.cpp
src/util/pcap_output.cpp
src/util/convert.cpp)
# linking comp / libs
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} -lpugixml -ltins)
================================================
FILE: pi_sniffer/pi_sniffer.conf
================================================
<pi_sniffer>
<wifidecrypt>
<key type="wep" bssid="00:12:bf:12:32:29" key="1f1f1f1f1f" />
<key type="wpa" ssid="Coherer" key="Induction" />
</wifidecrypt>
<output>
<dir path="/home/pi/pi_sniffer_output/" />
<file type="pcap" enabled="true" />
<file type="wigle" enabled="true" />
<file type="kml" enabled="true" />
<file type="client_csv" enabled="true"/>
<file type="probe_csv" enabled="true"/>
<file type="ap_clients_csv" enabled="true"/>
</output>
</pi_sniffer>
================================================
FILE: pi_sniffer/src/ap.cpp
================================================
#include "ap.hpp"
#include "util/convert.hpp"
AP::AP() :
m_bssid(),
m_lastseen(0),
m_firstseen(0),
m_lastSignal(0),
m_bestSignal(-100),
m_channel(0),
m_wps(false),
m_parsed_beacon(false),
m_ssid(),
m_mac(),
m_encryption(),
m_clients(0),
m_data(0),
m_accesslock(),
m_lat(0),
m_long(0),
m_alt(0),
m_bestLat(0),
m_bestLong(0),
m_bestAlt(0)
{
}
AP::~AP()
{
}
bool AP::has_wps() const
{
return m_wps;
}
void AP::set_wps(bool p_wps)
{
m_wps = p_wps;
}
void AP::set_last_seen(boost::uint32_t p_epoch_time)
{
if (m_firstseen == 0)
{
m_firstseen = p_epoch_time;
}
m_lastseen = p_epoch_time;
}
boost::uint32_t AP::get_last_seen() const
{
return m_lastseen;
}
boost::uint32_t AP::get_first_seen() const
{
return m_firstseen;
}
void AP::set_location_info(boost::int8_t p_signal,
double p_lat, double p_long,
double p_alt, bool p_gps)
{
if (p_signal == 0)
{
return;
}
m_lastSignal = p_signal;
if (p_gps)
{
boost::mutex::scoped_lock routerLock(m_accesslock);
m_lat = p_lat;
m_long = p_long;
m_alt = p_alt;
if (m_lastSignal > m_bestSignal)
{
m_bestSignal = m_lastSignal;
m_bestLat = p_lat;
m_bestLong = p_long;
m_bestAlt = p_alt;
}
}
else
{
if (m_lastSignal > m_bestSignal)
{
m_bestSignal = m_lastSignal;
}
}
}
boost::int8_t AP::get_last_signal() const
{
return m_lastSignal;
}
boost::int8_t AP::get_best_signal() const
{
return m_bestSignal;
}
double AP::get_latitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_lat;
}
double AP::get_best_latitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_bestLat;
}
double AP::get_longitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_long;
}
double AP::get_best_longitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_bestLong;
}
double AP::get_altitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_alt;
}
double AP::get_best_altitude()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_bestAlt;
}
void AP::set_ssid(const std::string& p_ssid)
{
// only accept ascii, I guess
for (unsigned int i = 0; i < p_ssid.size(); i++)
{
if (p_ssid[i] > 0x7e || p_ssid[i] < 0x20)
{
return;
}
}
boost::mutex::scoped_lock routerLock(m_accesslock);
m_ssid.assign(p_ssid);
}
std::string AP::get_ssid()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_ssid;
}
boost::uint64_t AP::get_bssid()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_bssid;
}
void AP::set_mac(const std::string& p_mac)
{
boost::mutex::scoped_lock routerLock(m_accesslock);
m_mac.assign(p_mac);
m_bssid = string_mac_to_int(p_mac);
}
std::string AP::get_mac()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_mac;
}
void AP::set_channel(boost::uint8_t p_channel)
{
m_channel = p_channel;
}
boost::uint8_t AP::get_channel() const
{
return m_channel;
}
void AP::set_encryption(const std::string& p_encryption)
{
boost::mutex::scoped_lock routerLock(m_accesslock);
m_encryption.assign(p_encryption);
}
std::string AP::get_encryption()
{
boost::mutex::scoped_lock routerLock(m_accesslock);
return m_encryption;
}
void AP::increment_client()
{
++m_clients;
}
boost::uint32_t AP::get_client_count() const
{
return m_clients;
}
void AP::increment_data_packet()
{
++m_data;
}
boost::uint32_t AP::get_data_count() const
{
return m_data;
}
void AP::set_beacon_parsed()
{
m_parsed_beacon = true;
}
bool AP::get_beacon_parsed() const
{
return m_parsed_beacon;
}
================================================
FILE: pi_sniffer/src/ap.hpp
================================================
#ifndef AP_HPP
#define AP_HPP
#include <string>
#include <boost/cstdint.hpp>
#include <boost/thread/mutex.hpp>
/*!
* This object represents an access point, typically a standard router but sometimes more
* interesting things like cars, doorbells, tablets, etc. This object attempts to track the
* general state and location of the the AP.
*
* Note that this object is r/w on at least two threads. I've made the assumption that
* int32 assignment is atomic, otherwise all other accesses require lock access.
*/
class AP
{
public:
AP();
~AP();
// Set to true if the WPS tag indicates the AP has WPS configured (0x02)
void set_wps(bool p_wps);
bool has_wps() const;
// Updated to track where we've seen the AP
void set_last_seen(boost::uint32_t p_time);
boost::uint32_t get_last_seen() const;
boost::uint32_t get_first_seen() const;
// Signal strength and GPS location information. We make no attempt to
// triangulate the actual location of the AP. Instead, we just call the "best"
// GPS coordinates whereever the signal strength is strongest. I think that
// is a fine enough solution.
void set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps);
boost::int8_t get_last_signal() const;
boost::int8_t get_best_signal() const;
double get_latitude();
double get_best_latitude();
double get_longitude();
double get_best_longitude();
double get_altitude();
double get_best_altitude();
// Store the broadcasted name. Currently only accepting ascii names.
void set_ssid(const std::string& p_ssid);
std::string get_ssid();
// Store the devices mac address
void set_mac(const std::string& p_mac);
std::string get_mac();
boost::uint64_t get_bssid();
// Store the channel we observed this device on
void set_channel(boost::uint8_t p_channel);
boost::uint8_t get_channel() const;
// Store a string representation of the encryption used (Open, WEP, WPA-EAP/PSK, WPA2-EAP/PSK)
void set_encryption(const std::string& p_encryption);
std::string get_encryption();
// Increment observed associated clients. This relies on a mechanism outside of the
// object to ensure that duplicate clients aren't being tracked.
void increment_client();
boost::uint32_t get_client_count() const;
// Track how much data we've seen go across this AP
void increment_data_packet();
boost::uint32_t get_data_count() const;
// We only want to parse this devices beacon/probe response once in order to save
// processing time.
void set_beacon_parsed();
bool get_beacon_parsed() const;
private:
AP(const AP& p_rhs);
AP& operator=(const AP& p_rhs);
private:
boost::uint64_t m_bssid;
boost::uint32_t m_lastseen;
boost::uint32_t m_firstseen;
boost::int8_t m_lastSignal;
boost::int8_t m_bestSignal;
boost::uint8_t m_channel;
bool m_wps;
bool m_parsed_beacon;
std::string m_ssid;
std::string m_mac;
std::string m_encryption;
boost::uint32_t m_clients;
boost::uint32_t m_data;
boost::mutex m_accesslock;
double m_lat;
double m_long;
double m_alt;
double m_bestLat;
double m_bestLong;
double m_bestAlt;
};
#endif
================================================
FILE: pi_sniffer/src/client.cpp
================================================
#include "client.hpp"
#include "util/convert.hpp"
Client::Client() :
m_lastseen(0),
m_firstseen(0),
m_associated_mac(0),
m_lastSignal(0),
m_bestSignal(-100),
m_accesslock(),
m_mac(),
m_lat(0),
m_long(0),
m_alt(0),
m_bestLat(0),
m_bestLong(0),
m_bestAlt(0)
{
}
Client::~Client()
{
}
void Client::set_last_seen(uint64_t p_epoch_time)
{
if (m_firstseen == 0)
{
m_firstseen = p_epoch_time;
}
m_lastseen = p_epoch_time;
}
boost::uint64_t Client::get_last_seen()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_lastseen;
}
boost::uint64_t Client::get_first_seen()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_firstseen;
}
void Client::set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps_on)
{
if (p_signal == 0)
{
return;
}
m_lastSignal = p_signal;
if (p_gps_on)
{
m_lat = p_lat;
m_long = p_long;
m_alt = p_alt;
if (m_lastSignal > m_bestSignal)
{
m_bestLat = p_lat;
m_bestLong = p_long;
m_bestAlt = p_alt;
m_bestSignal = m_lastSignal;
}
}
else if (m_lastSignal != 0)
{
if (m_lastSignal > m_bestSignal)
{
m_bestSignal = m_lastSignal;
}
}
}
boost::uint64_t Client::get_associated()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_associated_mac;
}
std::string Client::get_associated_str()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return printable_mac(reinterpret_cast<const unsigned char*>(&m_associated_mac), 6, true);
}
void Client::set_associated(boost::uint64_t p_associated_mac)
{
boost::mutex::scoped_lock clientLock(m_accesslock);
m_associated_mac = p_associated_mac;
}
boost::int8_t Client::get_last_signal() const
{
return m_lastSignal;
}
boost::int8_t Client::get_best_signal() const
{
return m_bestSignal;
}
double Client::get_latitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_lat;
}
double Client::get_best_latitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_bestLat;
}
double Client::get_longitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_long;
}
double Client::get_best_longitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_bestLong;
}
double Client::get_altitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_alt;
}
double Client::get_best_altitude()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_bestAlt;
}
void Client::set_mac(const std::string& p_mac)
{
boost::mutex::scoped_lock clientLock(m_accesslock);
m_mac.assign(p_mac);
}
std::string Client::get_mac()
{
boost::mutex::scoped_lock clientLock(m_accesslock);
return m_mac;
}
================================================
FILE: pi_sniffer/src/client.hpp
================================================
#ifndef CLIENT_HPP
#define CLIENT_HPP
#include <string>
#include <boost/thread.hpp>
/**
* This object represents an observed client. Clients should only be created if
* we observe it associated with an AP.
*
* Note that this object is r/w on at least two threads. I've made the assumption that
* int32 assignment is atomic, otherwise all other accesses require lock access.
*/
class Client
{
public:
Client();
~Client();
// Updated to track where we've seen the AP
void set_last_seen(boost::uint64_t p_epoch_time);
boost::uint64_t get_last_seen();
boost::uint64_t get_first_seen();
// Signal strength and GPS location information. We make no attempt to
// triangulate the actual location of the AP. Instead, we just call the "best"
// GPS coordinates whereever the signal strength is strongest. I think that
// is a fine enough solution.
void set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps_on);
boost::int8_t get_last_signal() const;
boost::int8_t get_best_signal() const;
double get_latitude();
double get_best_latitude();
double get_longitude();
double get_best_longitude();
double get_altitude();
double get_best_altitude();
// store the devices mac address
void set_mac(const std::string& p_mac);
std::string get_mac();
// store the mac of the station we are associated with
void set_associated(boost::uint64_t p_associated_mac);
boost::uint64_t get_associated();
std::string get_associated_str();
private:
Client(const Client& p_rhs);
Client& operator=(const Client& p_rhs);
private:
boost::uint64_t m_lastseen;
boost::uint64_t m_firstseen;
boost::uint64_t m_associated_mac;
boost::int8_t m_lastSignal;
boost::int8_t m_bestSignal;
boost::mutex m_accesslock;
std::string m_mac;
double m_lat;
double m_long;
double m_alt;
double m_bestLat;
double m_bestLong;
double m_bestAlt;
};
#endif
================================================
FILE: pi_sniffer/src/configuration.cpp
================================================
#include "configuration.hpp"
#include "util/convert.hpp"
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <pugixml.hpp>
Configuration::Configuration() :
m_wep_decrypter(),
m_wpa_decrypter(),
m_output_path(),
m_wep_keys(),
m_wpa_keys(),
m_pcap(false),
m_wigle(false),
m_kml(false),
m_client_csv(false),
m_probe_csv(false),
m_ap_clients_csv(false)
{
}
Configuration::~Configuration()
{
}
void Configuration::parse_configuration(const std::string& p_config_location)
{
pugi::xml_document config;
if (!config.load_file(p_config_location.c_str()))
{
throw std::runtime_error("Failed to load the configuration file: " + p_config_location);
}
const pugi::xml_node fullConfig = config.child("pi_sniffer");
if (fullConfig.empty())
{
throw std::runtime_error("Failed to find the root node: <pi_sniffer>");
}
const pugi::xml_node wifi_decrypt = fullConfig.child("wifidecrypt");
for (pugi::xml_node_iterator it = wifi_decrypt.begin();
it != wifi_decrypt.end(); ++it)
{
parse_wifi_key(*it);
}
const pugi::xml_node output = fullConfig.child("output");
for (pugi::xml_node_iterator it = output.begin();
it != output.end(); ++it)
{
parse_output(*it);
}
}
void Configuration::parse_wifi_key(const pugi::xml_node& p_key)
{
std::string key_type(p_key.attribute("type").as_string());
if (key_type.empty())
{
throw std::runtime_error("key missing the type attribute.");
}
std::string key(p_key.attribute("key").as_string());
if (key.empty())
{
throw std::runtime_error("key missing the key attribute.");
}
if (key_type == "wep")
{
std::string bssid(p_key.attribute("bssid").as_string());
if (bssid.empty())
{
throw std::runtime_error("key missing the bssid attribute.");
}
boost::uint64_t index = string_mac_to_int(bssid);
// convert to actual hex
std::string hex_key(string_to_hex(key));
if (hex_key.size() != 5 && hex_key.size() != 13 && hex_key.size() != 16)
{
throw std::runtime_error("The WEP key must be 5, 13, or 16 bytes long.");
}
m_wep_decrypter.add_password(int_mac_to_array(index), hex_key);
m_wep_keys.insert(bssid);
}
else if (key_type == "wpa")
{
std::string ssid(p_key.attribute("ssid").as_string());
if (ssid.empty())
{
throw std::runtime_error("key missing the ssid attribute.");
}
m_wpa_decrypter.add_ap_data(key, ssid);
m_wpa_keys.insert(ssid);
}
else
{
throw std::runtime_error("Unknown key type: " + key_type + ". Options are wep or wpa");
}
}
void Configuration::parse_output(const pugi::xml_node& p_output)
{
std::string type(p_output.attribute("type").as_string());
std::string path(p_output.attribute("path").as_string());
if (!type.empty())
{
if (type.compare("pcap") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_pcap = true;
}
}
else if (type.compare("wigle") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_wigle = true;
}
}
else if (type.compare("kml") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_kml = true;
}
}
else if (type.compare("client_csv") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_client_csv = true;
}
}
else if (type.compare("probe_csv") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_probe_csv = true;
}
}
else if (type.compare("ap_clients_csv") == 0)
{
std::string enabled(p_output.attribute("enabled").as_string());
if (enabled.compare("true") == 0)
{
m_ap_clients_csv = true;
}
}
}
else if (!path.empty())
{
m_output_path.assign(p_output.attribute("path").as_string());
if (!boost::filesystem::exists(m_output_path))
{
// create directory
try
{
boost::filesystem::create_directories(m_output_path);
}
catch (const std::exception&)
{
// ignore it
}
}
if (!boost::filesystem::is_directory(m_output_path))
{
throw std::runtime_error("The output path is not a directory.");
}
}
}
bool Configuration::has_wep_key(const std::string& p_bssid) const
{
return m_wep_keys.find(p_bssid) != m_wep_keys.end();
}
bool Configuration::has_wpa_key(const std::string& p_ssid) const
{
return m_wpa_keys.find(p_ssid) != m_wpa_keys.end();
}
================================================
FILE: pi_sniffer/src/configuration.hpp
================================================
#ifndef CONFIG_HPP
#define CONFIG_HPP
#include <boost/unordered_map.hpp>
#include <boost/cstdint.hpp>
#include <string>
#include <tins/crypto.h>
namespace pugi
{
struct xml_node;
}
/**
* Parses the configuration file passed on the command line. Also holds the information.
* The configuration information is largely:
*
* 1. What type of files to output.
* 2. Where to output the files.
* 3. Decryption keys.
*/
class Configuration
{
public:
Configuration();
~Configuration();
void parse_configuration(const std::string& p_config_location);
const std::string& get_output_path() const
{
return m_output_path;
}
bool get_pcap() const
{
return m_pcap;
}
bool get_wigle() const
{
return m_wigle;
}
bool get_kml() const
{
return m_kml;
}
bool get_client_csv() const
{
return m_client_csv;
}
bool get_probe_csv() const
{
return m_probe_csv;
}
bool get_ap_clients_csv() const
{
return m_ap_clients_csv;
}
bool has_wep_key(const std::string& p_bssid) const;
bool has_wpa_key(const std::string& p_ssid) const;
private:
void parse_wifi_key(const pugi::xml_node& p_key);
void parse_output(const pugi::xml_node& p_output);
public:
// Note: its a touch odd that configuration holds the decryptors but here we are
Tins::Crypto::WEPDecrypter m_wep_decrypter;
Tins::Crypto::WPA2Decrypter m_wpa_decrypter;
private:
//! The file path to the output directory
std::string m_output_path;
//! AP we have wep keys for
std::set<std::string> m_wep_keys;
//! AP we have wpa keys for
std::set<std::string> m_wpa_keys;
//! indicates if we should write ppi pcap file to disk
bool m_pcap;
//! indicates if we should write wigle csv to disk
bool m_wigle;
//! indicates if we should write out the kml information for routers
bool m_kml;
//! indicates if we should write out the clients to a csv file
bool m_client_csv;
//! indicates if we should write out the probes to a csv file
bool m_probe_csv;
//! indicates if we should write out the ap client csv file
bool m_ap_clients_csv;
};
#endif
================================================
FILE: pi_sniffer/src/input/kismet_drone.cpp
================================================
#include "kismet_drone.hpp"
#include "packet.hpp"
#include "stats.hpp"
#include <iostream>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>
namespace
{
// hold the packet data that goes down to the protocols
std::string s_drone_data;
#pragma pack(push, 1)
struct drone_trans_double
{
uint32_t mantissal;
uint32_t mantissah;
uint16_t exponent;
uint16_t sign;
};
struct ieee_double_t
{
unsigned int mantissal:32;
unsigned int mantissah:20;
unsigned int exponent:11;
unsigned int sign:1;
};
#pragma pack(pop)
void double_conversion_drone(double& x, drone_trans_double* y)
{
ieee_double_t* locfl = (ieee_double_t *)&(x);
(locfl)->mantissal = ntohl((y)->mantissal);
(locfl)->mantissah = ntohl((y)->mantissah);
(locfl)->exponent = ntohs((y)->exponent);
(locfl)->sign = ntohs((y)->sign);
}
}
KismetDrone::KismetDrone(const std::string& p_address, std::size_t p_port) :
m_ip(p_address),
m_port(),
m_io_service(),
m_socket(m_io_service),
m_deadline(m_io_service)
{
std::stringstream portString;
portString << p_port;
m_port = portString.str();
m_deadline.expires_at(boost::posix_time::pos_infin);
check_deadline();
}
KismetDrone::~KismetDrone()
{
close();
}
void KismetDrone::close()
{
try
{
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
m_socket.close();
}
catch (...)
{
}
}
void KismetDrone::check_deadline()
{
if (m_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
boost::system::error_code ignored_ec;
m_socket.close(ignored_ec);
m_deadline.expires_at(boost::posix_time::pos_infin);
}
m_deadline.async_wait(boost::lambda::bind(&KismetDrone::check_deadline, this));
}
bool KismetDrone::connect()
{
std::cout << "Drone connecting..." << std::endl;
try
{
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(m_ip), atoi(m_port.c_str()));
// set a 2 second deadline for the connection
boost::system::error_code ec = boost::asio::error::would_block;
m_deadline.expires_from_now(boost::posix_time::seconds(5));
m_socket.async_connect(endpoint, boost::lambda::var(ec) = boost::lambda::_1);
do
{
m_io_service.run_one();
}
while (ec == boost::asio::error::would_block);
if (ec || !m_socket.is_open())
{
return false;
}
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool KismetDrone::read(boost::uint32_t p_length, std::string& p_data)
{
boost::system::error_code ec = boost::asio::error::would_block;
m_deadline.expires_from_now(boost::posix_time::seconds(5));
boost::asio::streambuf response;
boost::asio::async_read(m_socket, response, boost::asio::transfer_exactly(p_length), boost::lambda::var(ec) = boost::lambda::_1);
do
{
m_io_service.run_one();
}
while (ec == boost::asio::error::would_block);
if (ec)
{
return false;
}
p_data.assign(std::istreambuf_iterator<char>(&response), std::istreambuf_iterator<char>());
response.consume(p_length);
return true;
}
bool KismetDrone::get_packet(Packet& p_packet)
{
boost::uint32_t type = 0;
boost::uint32_t read_length = 0;
boost::uint32_t offset = 0;
boost::uint32_t bitmap = 0;
while (type != 3)
{
if (!read(12, s_drone_data) || s_drone_data.size() != 12)
{
return false;
}
// check for sentinel
if (static_cast<boost::uint8_t>(s_drone_data[0]) != 0xde ||
static_cast<boost::uint8_t>(s_drone_data[3]) != 0xef)
{
return false;
}
// get the packet type (will indicate if we have a packet or not)
type = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 4);
type = ntohl(type);
// get length of the packet and read it all in
read_length = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 8);
read_length = ntohl(read_length);
if (!read(read_length, s_drone_data) || s_drone_data.size() != read_length)
{
return false;
}
bitmap = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0]);
bitmap = ntohl(bitmap);
if ((bitmap & 1) == 0)
{
// missing radio header.
type = 2;
continue;
}
offset = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 4);
offset = ntohl(offset) + 8;
if (read_length == 12 || offset == 8)
{
// This is an empty packet.
type = 2;
}
}
if ((offset + 44) >= read_length)
{
// invalid packet
return false;
}
if ((bitmap & 2) == 2)
{
// gps data present
boost::uint16_t gps_size = *reinterpret_cast<boost::uint16_t*>(&s_drone_data[0] + 38);
gps_size = ntohs(gps_size);
if (gps_size == 68)
{
p_packet.m_gps_on = true;
double_conversion_drone(p_packet.m_lat, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46));
double_conversion_drone(p_packet.m_long, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46 + sizeof(drone_trans_double)));
double_conversion_drone(p_packet.m_alt, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46 + (sizeof(drone_trans_double) * 2)));
}
}
// grab the signal metadata
boost::int16_t dbm = *reinterpret_cast<boost::int16_t*>(&s_drone_data[0] + 18);
dbm = ntohs(dbm);
// grab the metadata in the radio header
boost::uint32_t time = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + (offset + 28));
time = ntohl(time);
// skip over the metadata
p_packet.m_data = reinterpret_cast<const boost::uint8_t*>(&s_drone_data[0]) + (offset + 44);
p_packet.m_length = read_length - (offset + 44);
// update stats
p_packet.m_stats.increment_packets();
p_packet.m_time = time;
p_packet.m_signal = dbm;
return true;
}
================================================
FILE: pi_sniffer/src/input/kismet_drone.hpp
================================================
#ifndef KISMET_DRONE_HPP
#define KISMET_DRONE_HPP
#include <cstddef>
#include <string>
#include <boost/asio.hpp>
class Packet;
class KismetDrone
{
public:
KismetDrone(const std::string& p_address, std::size_t p_port);
~KismetDrone();
bool get_packet(Packet& p_packet);
bool connect();
private:
bool read(boost::uint32_t p_length, std::string& p_data);
void check_deadline();
void close();
private:
KismetDrone(const KismetDrone& p_rhs);
KismetDrone& operator=(const KismetDrone& p_rhs);
private:
// the ip address to connect to
std::string m_ip;
// the port to connect to
std::string m_port;
// the IO service associated with our blocking socket
boost::asio::io_service m_io_service;
// the blocking socket we use for communication
boost::asio::ip::tcp::socket m_socket;
// Timer to use with async socket operations
boost::asio::deadline_timer m_deadline;
};
#endif
================================================
FILE: pi_sniffer/src/input/pcap.cpp
================================================
#include "pcap.hpp"
#include "packet.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/concept_check.hpp>
#include <boost/static_assert.hpp>
namespace
{
unsigned char data[65535] = {0};
#pragma pack(push, 1)
struct pcap_header
{
boost::uint32_t magic_number;
boost::uint16_t version_major;
boost::uint16_t version_minor;
boost::uint32_t thiszone;
boost::uint32_t sigfigs;
boost::uint32_t snaplen;
boost::uint32_t network;
};
struct packet_header
{
boost::uint32_t ts_sec;
boost::uint32_t ts_usec;
boost::uint32_t incl_len;
boost::uint32_t orig_len;
};
struct ppi_packetheader
{
boost::uint8_t pph_version;
boost::uint8_t pph_flags;
boost::uint16_t pph_len;
boost::uint32_t pph_dlt;
};
struct ppi_fieldheader
{
boost::uint16_t pfh_type;
boost::uint16_t pfh_datalen;
};
struct gps_fields
{
boost::uint8_t gps_revision;
boost::uint8_t gps_pad;
boost::uint16_t gps_length;
boost::uint32_t gps_present;
boost::uint32_t gps_lat;
boost::uint32_t gps_long;
boost::uint32_t gps_alt;
boost::uint32_t gps_app;
};
struct radiotap_header
{
boost::uint8_t version;
boost::uint8_t pad;
boost::uint16_t len;
boost::uint32_t present;
};
struct ppi_common
{
boost::uint64_t tsft;
boost::uint16_t flags;
boost::uint16_t rate;
boost::uint16_t frequency;
boost::uint16_t channel_type;
boost::uint8_t hopset;
boost::uint8_t pattern;
boost::uint8_t rssi;
boost::uint8_t noise;
};
#pragma pack(pop)
// harris crap
double fixed_3_7_to_flt(boost::uint32_t in)
{
boost::int32_t remapped = in - (180 * 10000000);
return static_cast<double>(remapped) / 10000000;
}
double fixed_6_4_to_flt(boost::uint32_t in)
{
boost::int32_t remapped_in = in - (180000 * 10000);
double ret = (double) ((double) remapped_in / 10000);
return ret;
}
}
PCAP::PCAP(std::string p_filename) :
m_file(p_filename.c_str(), std::ios::binary),
m_linktype(0)
{
}
PCAP::~PCAP()
{
}
bool PCAP::initialize()
{
if (!m_file.is_open())
{
return false;
}
m_file.read(reinterpret_cast<char*>(&data[0]), sizeof(pcap_header));
if (m_file.gcount() != sizeof(pcap_header))
{
return false;
}
struct pcap_header* header = reinterpret_cast<struct pcap_header*>(data);
if (header->magic_number != 0xa1b2c3d4)
{
return false;
}
if (header->network != 192 && header->network != 127 && header->network != 105)
{
return false;
}
m_linktype = header->network;
return true;
}
bool PCAP::eof() const
{
return m_file.eof() && m_file.good();
}
bool PCAP::get_packet(Packet& p_packet)
{
m_file.read(reinterpret_cast<char*>(&data[0]), sizeof(packet_header));
if (m_file.gcount() != sizeof(packet_header))
{
return false;
}
struct packet_header* header = reinterpret_cast<struct packet_header*>(data);
p_packet.m_time = header->ts_sec;
p_packet.m_stats.increment_packets();
boost::uint32_t length = header->incl_len;
m_file.read(reinterpret_cast<char*>(&data[0]), length);
if (m_file.gcount() != static_cast<std::streamsize>(length) ||
length < static_cast<std::size_t>(4))
{
return false;
}
switch (m_linktype)
{
case 127:
return do_radiotap(p_packet, length);
case 192:
return do_ppi(p_packet, length);
default:
//uhm... ok
break;
}
p_packet.m_data = data;
p_packet.m_length = length;
return true;
}
bool PCAP::do_radiotap(Packet& p_packet, boost::uint32_t p_length)
{
struct radiotap_header* radio_header = reinterpret_cast<struct radiotap_header*>(data);
if (radio_header->version != 0)
{
return false;
}
if (p_length < radio_header->len)
{
return false;
}
bool has_fcs = false;
unsigned char* radio_tags = data + sizeof(radiotap_header);
if ((radio_header->present & 0x01) != 0)
{
// mac timestamp
radio_tags += 8;
}
if ((radio_header->present & 0x02) != 0)
{
// flags
unsigned char flags = radio_tags[0];
if (flags & 0x10)
{
has_fcs = true;
}
radio_tags += 1;
}
if ((radio_header->present & 0x04) != 0)
{
// rate
radio_tags += 1;
}
if ((radio_header->present & 0x08) != 0)
{
// channel frequency / type
radio_tags += 4;
}
if ((radio_header->present & 0x10) != 0)
{
// fhss
radio_tags += 2;
}
if ((radio_header->present & 0x20) != 0)
{
//signal
p_packet.m_signal = radio_tags[0];
}
p_packet.m_data = data + radio_header->len;
p_packet.m_length = p_length - radio_header->len;
if (has_fcs)
{
p_packet.m_length -= 4;
}
return true;
}
bool PCAP::do_ppi(Packet& p_packet, boost::uint32_t p_length)
{
struct ppi_packetheader* ppi_header = reinterpret_cast<struct ppi_packetheader*>(data);
if (ppi_header->pph_version != 0)
{
return false;
}
if (ppi_header->pph_dlt != 105)
{
return false;
}
if (p_length < ppi_header->pph_len )
{
return false;
}
// check the next field type
struct ppi_fieldheader* field_header = reinterpret_cast<struct ppi_fieldheader*>(data + sizeof(ppi_packetheader));
if (field_header->pfh_type == 0x7532)
{
// gps info
struct gps_fields* gps = reinterpret_cast<struct gps_fields*>(data + sizeof(ppi_packetheader) + sizeof(ppi_fieldheader));
if (gps->gps_length == field_header->pfh_datalen)
{
if (gps->gps_present == 0x2000000e)
{
p_packet.m_gps_on = true;
p_packet.m_lat = fixed_3_7_to_flt(gps->gps_lat);
p_packet.m_long = fixed_3_7_to_flt(gps->gps_long);
p_packet.m_alt = fixed_6_4_to_flt(gps->gps_alt);
// try the next field
field_header = reinterpret_cast<struct ppi_fieldheader*>(
reinterpret_cast<char*>(gps) + gps->gps_length);
}
}
}
if (field_header->pfh_type == 0x0002)
{
// 802.11 common
struct ppi_common* common = reinterpret_cast<struct ppi_common*>(reinterpret_cast<char*>(field_header) + 4);
p_packet.m_signal = common->rssi;
}
p_packet.m_data = data + ppi_header->pph_len;
p_packet.m_length = p_length - ppi_header->pph_len;
return true;
}
================================================
FILE: pi_sniffer/src/input/pcap.hpp
================================================
#ifndef PCAP_HPP
#define PCAP_HPP
#include <fstream>
#include <string>
#include <boost/cstdint.hpp>
class Packet;
class PCAP
{
public:
PCAP(std::string p_filename);
~PCAP();
bool initialize();
bool eof() const;
bool get_packet(Packet& p_packet);
private:
bool do_radiotap(Packet& p_packet, boost::uint32_t p_length);
bool do_ppi(Packet& p_packet, boost::uint32_t p_length);
private:
std::ifstream m_file;
std::size_t m_linktype;
};
#endif
================================================
FILE: pi_sniffer/src/main.cpp
================================================
#include <cstdlib>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/program_options.hpp>
#include "util/convert.hpp"
#include "ap.hpp"
#include "client.hpp"
#include "packet.hpp"
#include "protocols/ieee80211.hpp"
#include "input/kismet_drone.hpp"
#include "input/pcap.hpp"
namespace
{
bool parseCommandLine(int p_argCount, char* p_argArray[],
std::string& p_server_address, std::size_t& p_server_port, std::string& p_file, Configuration& p_config)
{
boost::program_options::options_description description("options");
description.add_options()("help,h", "A list of command line options")
("version,v", "Display version information")
("config,c", boost::program_options::value<std::string>()->default_value(std::string("/home/pi/pi_sniffer/pi_sniffer.conf"), ""), "The path to the configuration file")
("file,f", boost::program_options::value<std::string>(),"The file to parse.")
("kismet-address,k", boost::program_options::value<std::string>(), "The address of the kismet server.")
("kismet-port,p", boost::program_options::value<std::size_t>(), "The port of the kismet server.");
boost::program_options::variables_map argv_map;
try
{
boost::program_options::store(
boost::program_options::parse_command_line(
p_argCount, p_argArray, description), argv_map);
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n" << std::endl;
std::cout << description << std::endl;
return false;
}
boost::program_options::notify(argv_map);
if (argv_map.empty() || argv_map.count("help"))
{
std::cout << description << std::endl;
return false;
}
if (argv_map.count("version"))
{
std::cout << "🦞 Pi Sniffer Alpha 🦞" << std::endl;
return false;
}
if (argv_map.count("config"))
{
try
{
p_config.parse_configuration(argv_map["config"].as<std::string>());
}
catch (const std::runtime_error& e)
{
std::cerr << "Failed config parsing: " << e.what() << std::endl;
return false;
}
}
if (argv_map.count("file"))
{
p_file = argv_map["file"].as<std::string>();
return true;
}
if (argv_map.count("kismet-port") && argv_map.count("kismet-address"))
{
p_server_port = argv_map["kismet-port"].as<std::size_t>();
p_server_address = argv_map["kismet-address"].as<std::string>();
return true;
}
return false;
}
void fileThread(Packet& p_packet, const std::string& p_file)
{
IEEE80211 link_layer;
PCAP file_input(p_file);
file_input.initialize();
std::cout << "Reading: " << p_file << std::endl;
while (file_input.get_packet(p_packet))
{
link_layer.handle_packet(p_packet);
p_packet.reset();
}
}
void protocolThread(Packet& p_packet, const std::string& p_kismet_address, const std::size_t p_kismet_port)
{
try
{
IEEE80211 link_layer;
while (!p_packet.m_shutdown)
{
KismetDrone input(p_kismet_address, p_kismet_port);
input.connect();
while (!p_packet.m_shutdown && input.get_packet(p_packet))
{
link_layer.handle_packet(p_packet);
p_packet.reset();
}
if (!p_packet.m_shutdown)
{
// for some reason we lost sync with kismet? I've seen kismet simply stop sending data as
// well. Which is strange, but I think not our fault. Sleep 5 seconds and let try to
// reconnect.
sleep(5);
}
}
}
catch (const std::runtime_error& e)
{
}
}
}
int main(int p_argCount, char* p_argArray[])
{
/* for better or worse, holds on global structures used by protocol
* processing and the user interface */
try
{
Packet packet;
std::stringstream timeStream;
boost::uint32_t start = time(NULL);
timeStream << start;
packet.m_startTime.assign(timeStream.str());
std::string file;
std::string interface;
std::string kismet_address;
std::size_t kismet_port = 0;
if (!parseCommandLine(p_argCount, p_argArray, kismet_address, kismet_port, file, packet.get_config()))
{
return EXIT_FAILURE;
}
// spawn protocol thread
boost::thread protoThread;
if (file.empty())
{
protoThread = boost::thread(protocolThread, boost::ref(packet), kismet_address, kismet_port);
}
else
{
protoThread = boost::thread(fileThread, boost::ref(packet), file);
}
// ui server will be on the main thread
boost::asio::io_service io_service;
boost::asio::ip::udp::socket ui_sock(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 1270));
while (packet.m_shutdown == false)
{
boost::array<char, 128> recv_buf;
boost::system::error_code error;
boost::asio::ip::udp::endpoint remote_endpoint;
int length = ui_sock.receive_from(boost::asio::buffer(recv_buf), remote_endpoint, 0, error);
if (error && error != boost::asio::error::message_size)
{
packet.m_shutdown = true;
continue;
}
if (length == 2 && recv_buf.data()[0] == 's')
{
packet.m_shutdown = true;
continue;
}
else if (length == 2 && recv_buf.data()[0] == 'o')
{
std::stringstream response;
response << (time(NULL) - start) << ","
<< (packet.m_stats.get_unencrypted() + packet.m_stats.get_wep() + packet.m_stats.get_wpa()) << ","
<< packet.m_stats.get_unencrypted() << ","
<< packet.m_stats.get_wep() << ","
<< packet.m_stats.get_wpa() << ","
<< packet.m_stats.get_packets() << ","
<< packet.m_stats.get_beacons() << ","
<< packet.m_stats.get_data_packets() << ","
<< packet.m_stats.get_encrypted() << ","
<< packet.m_stats.get_eapol() << std::endl;
boost::system::error_code ignored_error;
ui_sock.send_to(boost::asio::buffer(response.str()), remote_endpoint, 0, ignored_error);
}
else if (length == 2 && recv_buf.data()[0] == 'l')
{
std::stringstream response;
std::vector<AP*> recent;
packet.get_recent_ap(30, recent);
for (std::vector<AP*>::iterator it = recent.begin();
it != recent.end(); ++it)
{
response << (*it)->get_ssid() << "," << (*it)->get_mac() << std::endl;
}
response << std::endl;
std::string output(response.str());
if (output.size() == 1)
{
output.push_back('\n');
}
boost::system::error_code ignored_error;
ui_sock.send_to(boost::asio::buffer(output), remote_endpoint, 0, ignored_error);
}
else if (length == 2 && recv_buf.data()[0] == 'c')
{
std::stringstream response;
std::vector<Client*> recent;
packet.get_recent_client(30, recent);
for (std::vector<Client*>::iterator it = recent.begin();
it != recent.end(); ++it)
{
response << (*it)->get_mac() << std::endl;
}
response << std::endl;
std::string output(response.str());
if (output.size() == 1)
{
output.push_back('\n');
}
boost::system::error_code ignored_error;
ui_sock.send_to(boost::asio::buffer(output), remote_endpoint, 0, ignored_error);
}
else if (length == 19 && recv_buf.data()[0] == 'r')
{
std::string mac(recv_buf.data() + 1, 17);
boost::uint64_t lookup_mac = string_mac_to_int(mac);
AP* router = packet.find_ap(lookup_mac);
if (router != NULL)
{
std::stringstream result;
result << ((int)router->get_channel() & 0xff) << ","
<< router->get_encryption() << ","
<< ((int)router->get_last_signal()) << ","
<< router->get_client_count() << std::endl << std::endl;
boost::system::error_code ignored_error;
ui_sock.send_to(boost::asio::buffer(result.str()), remote_endpoint, 0, ignored_error);
}
}
else if (length == 19 && recv_buf.data()[0] == 'c')
{
std::string mac(recv_buf.data() + 1, 17);
boost::uint64_t lookup_mac = string_mac_to_int(mac);
Client* client = packet.find_client(lookup_mac, false);
if (client != NULL)
{
std::stringstream result;
result << ((int)client->get_last_signal()) << ","
<< client->get_associated_str() << std::endl << std::endl;
boost::system::error_code ignored_error;
ui_sock.send_to(boost::asio::buffer(result.str()), remote_endpoint, 0, ignored_error);
}
}
else if (length == '2' && recv_buf.data()[0] == 'f')
{
// flash output
if (packet.get_const_config().get_wigle())
{
packet.write_wigle_output(packet.m_startTime);
}
if (packet.get_const_config().get_kml())
{
packet.write_kml_output(packet.m_startTime);
}
if (packet.get_const_config().get_client_csv())
{
packet.write_client_csv_output(packet.m_startTime);
}
if (packet.get_const_config().get_probe_csv())
{
packet.write_probe_csv_output(packet.m_startTime);
}
if (packet.get_const_config().get_ap_clients_csv())
{
packet.write_ap_clients_csv_output(packet.m_startTime);
}
}
}
// join the protocol thread back in
packet.m_shutdown = true;
protoThread.interrupt();
protoThread.join();
if (packet.get_const_config().get_wigle())
{
packet.write_wigle_output(packet.m_startTime);
}
if (packet.get_const_config().get_kml())
{
packet.write_kml_output(packet.m_startTime);
}
if (packet.get_const_config().get_client_csv())
{
packet.write_client_csv_output(packet.m_startTime);
}
if (packet.get_const_config().get_probe_csv())
{
packet.write_probe_csv_output(packet.m_startTime);
}
if (packet.get_const_config().get_ap_clients_csv())
{
packet.write_ap_clients_csv_output(packet.m_startTime);
}
}
catch (const std::runtime_error& e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
catch (const std::exception& e)
{
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
================================================
FILE: pi_sniffer/src/packet.cpp
================================================
#include "packet.hpp"
#include "client.hpp"
#include "ap.hpp"
#include "probed_network.hpp"
#include "util/convert.hpp"
#include "util/kml_maker.hpp"
#include <ctime>
#include <iostream>
#include <fstream>
Packet::Packet() :
m_devices(),
m_clients(),
m_probed_networks(),
m_router_mutex(),
m_client_mutex(),
m_probe_mutex(),
m_configuration(),
m_stats(),
m_data(NULL),
m_length(0),
m_time(0),
m_lat(0),
m_long(0),
m_alt(0),
m_signal(0),
m_gps_on(false),
m_current_client(NULL),
m_current_router(NULL),
m_from_client(false),
m_startTime(),
m_shutdown(false)
{
}
Packet::~Packet()
{
}
void Packet::reset()
{
// note: don't zero out m_time
m_data = NULL;
m_length = 0;
m_signal = 0;
m_lat = 0;
m_long = 0;
m_alt = 0;
m_gps_on = false;
}
const Configuration& Packet::get_const_config() const
{
return m_configuration;
}
Configuration& Packet::get_config()
{
return m_configuration;
}
void Packet::get_recent_ap(boost::uint32_t p_seconds, std::vector<AP*>& p_routers)
{
// this approach has the short coming that we need packets to be regurlarly flowing.
// although hopefully that's the case.
boost::uint32_t cutoff = m_time - p_seconds;
// loop over everything. yes there are better approaches but they require much more code
// and I don't think the router map should ever get big enough for that to pay off
// get read lock for router lookup
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator iter = m_devices.begin();
iter != m_devices.end(); ++iter)
{
if (iter->second->get_last_seen() >= cutoff)
{
std::vector<AP*>::iterator v_iter = p_routers.begin();
for ( ; v_iter != p_routers.end(); ++v_iter)
{
if ((*v_iter)->get_last_seen() < iter->second->get_last_seen())
{
break;
}
}
p_routers.insert(v_iter, iter->second);
}
}
}
void Packet::get_recent_client(boost::uint32_t p_seconds, std::vector<Client*>& p_clients)
{
// this approach has the short coming that we need packets to be regurlarly flowing.
// although hopefully that's the case.
boost::uint32_t cutoff = m_time - p_seconds;
// loop over everything. yes there are better approaches but they require much more code
// and I don't think the router map should ever get big enough for that to pay off
// get read lock for router lookup
boost::upgrade_lock<boost::shared_mutex> readLock(m_client_mutex);
for (boost::ptr_unordered_map<boost::uint64_t, Client>::iterator iter = m_clients.begin();
iter != m_clients.end(); ++iter)
{
if (iter->second->get_last_seen() >= cutoff)
{
std::vector<Client*>::iterator v_iter = p_clients.begin();
for ( ; v_iter != p_clients.end(); ++v_iter)
{
if ((*v_iter)->get_last_seen() < iter->second->get_last_seen())
{
break;
}
}
p_clients.insert(v_iter, iter->second);
}
}
}
AP* Packet::find_ap(boost::uint64_t p_mac)
{
{
// get read lock for router lookup
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
// find the router and return it if it exists
boost::ptr_unordered_map<boost::uint64_t, AP>::iterator iter = m_devices.find(p_mac);
if (iter != m_devices.end())
{
m_current_router = (*iter).second;
m_current_router->set_last_seen(m_time);
if (m_gps_on)
{
m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
}
else if (m_signal != 0)
{
m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
}
return m_current_router;
}
// upgrade to a write lock and create the new router object
boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);
m_current_router = &m_devices[p_mac];
}
// this is a new router so update it.
unsigned char* mac = reinterpret_cast<unsigned char*>(&p_mac);
std::string mac_string(printable_mac(mac, 6));
m_current_router->set_mac(mac_string);
m_current_router->set_last_seen(m_time);
if (m_gps_on)
{
m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
}
else if (m_signal != 0)
{
m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
}
return m_current_router;
}
Client* Packet::find_client(boost::uint64_t p_src_mac, bool p_associated)
{
{
// get read lock for client lookup
boost::upgrade_lock<boost::shared_mutex> readLock(m_client_mutex);
// if the client already exists we can simply return it
boost::ptr_unordered_map<boost::uint64_t, Client>::iterator iter = m_clients.find(p_src_mac);
if (iter != m_clients.end())
{
m_current_client = (*iter)->second;
m_current_client->set_last_seen(m_time);
m_current_client->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
if (m_current_router && p_associated && m_current_client->get_associated() == 0)
{
m_current_client->set_associated(m_current_router->get_bssid());
m_current_router->increment_client();
}
return m_current_client;
}
// upgrade to a write lock and create the new client object
boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);
m_current_client = &m_clients[p_src_mac];
}
if (m_current_router && p_associated)
{
m_current_client->set_associated(m_current_router->get_bssid());
m_current_router->increment_client();
}
unsigned char* mac = reinterpret_cast<unsigned char*>(&p_src_mac);
std::string mac_address(printable_mac(mac, 6));
m_current_client->set_mac(mac_address);
m_current_client->set_last_seen(m_time);
m_current_client->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);
return m_current_client;
}
void Packet::write_wigle_output(const std::string& p_time)
{
std::string filename(m_configuration.get_output_path() + "pi_sniffer_wigle_" + p_time + ".csv");
// create the file
std::filebuf wigle_output;
wigle_output.open(filename, std::ios::out);
if (!wigle_output.is_open())
{
std::cerr << "Failed to write " << filename << std::endl;
return;
}
std::ostream os(&wigle_output);
// header
os << "WigleWifi-1.4\n";
// data fields
os << "MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n";
// time
char buffer[32] = {0};
std::string time;
// loop over the router
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator it = m_devices.begin();
it != m_devices.end(); ++it)
{
os << it->second->get_mac() << ",";
if (it->second->get_ssid() == "<Unknown>")
{
os << ",";
}
else
{
os << it->second->get_ssid() << ",";
}
if (it->second->get_encryption().find("/") != std::string::npos)
{
os << "[WPA-PSK][WPA2-PSK]";
}
else if (it->second->get_encryption() == "None")
{
//leave it blank
}
else
{
os << "[" << it->second->get_encryption() << "]";
}
if (it->second->has_wps())
{
os << "[WPS]";
}
os << ",";
time_t start = it->second->get_first_seen();
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localtime(&start));
time.assign(buffer);
os << time << "," << (int)it->second->get_channel() << ",";
os << static_cast<int>(it->second->get_best_signal()) << ",";
os << it->second->get_best_latitude() << ",";
os << it->second->get_best_longitude() << ",";
os << it->second->get_best_altitude() << ",";
os << ","; // don't really have accuracy info
os << "WIFI" << "\n";
}
// close it
wigle_output.close();
}
void Packet::write_kml_output(const std::string& p_time)
{
std::string filename(m_configuration.get_output_path() + "pi_sniffer_map_" + p_time);
KML_Maker kml_maker;
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
kml_maker.load_aps(m_devices);
kml_maker.write_all(filename);
}
void Packet::write_client_csv_output(const std::string& p_time)
{
std::string filename(m_configuration.get_output_path() + "pi_sniffer_clients_" + p_time + ".csv");
// create the file
std::filebuf client_output;
client_output.open(filename, std::ios::out);
if (!client_output.is_open())
{
std::cerr << "Failed to write " << filename << std::endl;
return;
}
std::ostream os(&client_output);
// data fields
os << "MAC,BSSID,RSSI,Lat,Long,FirstSeen,LastSeen" << std::endl;
// loop over the router
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
for (boost::ptr_unordered_map<boost::uint64_t, Client>::iterator it = m_clients.begin();
it != m_clients.end(); ++it)
{
os << it->second->get_mac() << ",";
os << it->second->get_associated_str() << ",";
os << static_cast<int>(it->second->get_best_signal()) << ",";
os << it->second->get_best_latitude() << ",";
os << it->second->get_best_longitude() << ",";
os << it->second->get_first_seen() << ",";
os << it->second->get_last_seen() << std::endl;
}
client_output.close();
}
void Packet::write_probe_csv_output(const std::string& p_time)
{
std::string filename(m_configuration.get_output_path() + "pi_sniffer_probes_" + p_time + ".csv");
// create the file
std::filebuf client_output;
client_output.open(filename, std::ios::out);
if (!client_output.is_open())
{
std::cerr << "Failed to write " << filename << std::endl;
return;
}
std::ostream os(&client_output);
os << "Probe,Count" << std::endl;
boost::upgrade_lock<boost::shared_mutex> readLock(m_probe_mutex);
for (boost::ptr_map<std::string, Probed_Network>::iterator it = m_probed_networks.begin();
it != m_probed_networks.end(); ++it)
{
os << it->first << "," << it->second->get_clients_count() << std::endl;
}
client_output.close();
}
void Packet::write_ap_clients_csv_output(const std::string& p_time)
{
std::string filename(m_configuration.get_output_path() + "pi_sniffer_ap_clients_" + p_time + ".csv");
// create the file
std::filebuf ap_clients_output;
ap_clients_output.open(filename, std::ios::out);
if (!ap_clients_output.is_open())
{
std::cerr << "Failed to write " << filename << std::endl;
return;
}
std::ostream os(&ap_clients_output);
// data fields
os << "Clients,SSID,MAC,\n";
// loop over the router
boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);
for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator it = m_devices.begin();
it != m_devices.end(); ++it)
{
if (it->second->get_mac() == "00:00:00:00:00:00")
{
continue;
}
os << it->second->get_client_count() << ",";
if (it->second->get_ssid() == "<Unknown>")
{
os << ",";
}
else
{
os << it->second->get_ssid() << ",";
}
os << it->second->get_mac() << std::endl;
}
// close it
ap_clients_output.close();
}
void Packet::add_probe_network(const std::string& p_network, const std::string& p_client)
{
if (p_network.size() < 3)
{
return;
}
// only accept ascii, I guess
for (unsigned int i = 0; i < p_network.size(); i++)
{
if (p_network[i] > 0x7e || p_network[i] < 0x20)
{
return;
}
}
{
// get read lock for client lookup
boost::upgrade_lock<boost::shared_mutex> readLock(m_probe_mutex);
if (m_probed_networks.find(p_network) == m_probed_networks.end())
{
// upgrade to a write lock and insert the new probe network
boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);
Probed_Network* new_probe = &m_probed_networks[p_network];
new_probe->set_name(p_network);
new_probe->add_client(string_mac_to_int(p_client));
}
else
{
// upgrade to a write lock and add the client to the prexisting probe network
boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);
m_probed_networks[p_network].add_client(string_mac_to_int(p_client));
}
}
}
================================================
FILE: pi_sniffer/src/packet.hpp
================================================
#ifndef PACKET_HPP
#define PACKET_HPP
#include <string>
#include <vector>
#include <boost/thread.hpp>
#include <boost/cstdint.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_unordered_map.hpp>
#include "stats.hpp"
#include "configuration.hpp"
class Client;
class AP;
class Probed_Network;
/**
* Packet is an unfortunate design decision. It, more or less, holds or owns all the
* objects in the system. This is done for convience so that all protocols have access
* to all data. It's also manipulated by both the UI thread and the packet thread.
*
* On the packet side, packets are read into the data member variable and the object
* is sent down the protocol stack (80211 -> snap -> eapol).
*
* On the UI side, the packet's devices/client maps are queried as the user sees fit.
*
* As mentioned, this object (in particularly the maps) is accessed via two threads
* so be cautious.
*/
class Packet
{
public:
Packet();
~Packet();
// resets the member variables associated with packet data
void reset();
// find the access point with the given mac in m_devices
AP* find_ap(boost::uint64_t p_mac);
// find the client with the given mac in m_clients
Client* find_client(boost::uint64_t p_mac, bool p_associated);
// store a probe request
void add_probe_network(const std::string& p_network, const std::string& p_client);
// get a const reference to the configuration
const Configuration& get_const_config() const;
// get the configuration
Configuration& get_config();
// get all access points seen in the last p_seconds seconds
void get_recent_ap(boost::uint32_t p_seconds, std::vector<AP*>& p_routers);
// get all clients seen in the last p_seconds seconds
void get_recent_client(boost::uint32_t p_seconds, std::vector<Client*>& p_clients);
// file output functions
void write_wigle_output(const std::string& p_time);
void write_kml_output(const std::string& p_time);
void write_client_csv_output(const std::string& p_time);
void write_probe_csv_output(const std::string& p_time);
void write_ap_clients_csv_output(const std::string& p_time);
private:
Packet(const Packet& p_rhs);
Packet& operator=(const Packet& p_rhs);
private:
// All observered routers: mac to router mapping
boost::ptr_unordered_map<boost::uint64_t, AP> m_devices;
// All observed clients: mac to client mapping
boost::ptr_unordered_map<boost::uint64_t, Client> m_clients;
// All the probe requests seend (ssid -> Probed object)
boost::ptr_map<std::string, Probed_Network> m_probed_networks;
// mutex for accessing m_devices
boost::shared_mutex m_router_mutex;
// mutex for accessing m_clients
boost::shared_mutex m_client_mutex;
// mutex for accessing probes
boost::shared_mutex m_probe_mutex;
// holds the parsed configuration data
Configuration m_configuration;
public:
// Holds basic counter statistics
Stats m_stats;
// the below should *only* be accessed via the packet thread (except m_shudown)
// packet information provided by the input method (pcap or kismet)
const unsigned char* m_data;
std::size_t m_length;
boost::uint32_t m_time;
double m_lat;
double m_long;
double m_alt;
boost::int8_t m_signal;
bool m_gps_on;
// cached result of the current devices we are operating on
Client* m_current_client;
AP* m_current_router;
bool m_from_client;
// created when the program starts up. largely used for output
std::string m_startTime;
// cross thread indicator that its time to shutdown
bool m_shutdown;
};
#endif
================================================
FILE: pi_sniffer/src/probed_network.cpp
================================================
#include "probed_network.hpp"
Probed_Network::Probed_Network() :
m_accesslock(),
m_name(),
m_clients()
{
}
Probed_Network::~Probed_Network()
{
}
void Probed_Network::set_name(const std::string& p_name)
{
m_name.assign(p_name);
}
void Probed_Network::add_client(boost::uint64_t p_mac)
{
m_clients.insert(p_mac);
}
================================================
FILE: pi_sniffer/src/probed_network.hpp
================================================
#ifndef PROBED_NETWORK_HPP
#define PROBED_NETWORK_HPP
#include <set>
#include <string>
#include <boost/cstdint.hpp>
#include <boost/thread/mutex.hpp>
/**
* A simple object that stores an SSID that was probed and all the client macs
* that probed it. This *can be* prob
*/
class Probed_Network
{
public:
Probed_Network();
~Probed_Network();
// stores the name probed for
void set_name(const std::string& p_name);
// add a client to the set of STA that probed this ssid
void add_client(boost::uint64_t p_mac);
std::size_t get_clients_count() const
{
return m_clients.size();
}
private:
boost::mutex m_accesslock;
std::string m_name;
std::set<boost::uint64_t> m_clients;
};
#endif
================================================
FILE: pi_sniffer/src/protocols/eapol11.cpp
================================================
#include "eapol11.hpp"
#include "packet.hpp"
#include "ap.hpp"
#include <netinet/in.h>
#include <boost/static_assert.hpp>
namespace
{
#pragma pack(push, 1)
struct key_struct
{
boost::uint8_t type;
boost::uint16_t key_info;
boost::uint16_t key_length;
boost::uint8_t replay_counter[8];
boost::uint8_t key_nonce[32];
boost::uint8_t key_iv[16];
boost::uint8_t key_rsc[8];
boost::uint8_t key_id[8];
boost::uint8_t key_mic[16];
boost::uint16_t key_data_length;
};
BOOST_STATIC_ASSERT(sizeof(key_struct) == 95);
#pragma pack(pop)
}
EAPOL::EAPOL()
{
}
EAPOL::~EAPOL()
{
}
bool EAPOL::handle_packet(Packet& p_packet)
{
// too short to be an eapol (or an interesting one at least)
if (p_packet.m_length < 4)
{
return false;
}
const boost::uint16_t length =
ntohs(*reinterpret_cast<const boost::uint16_t*>(p_packet.m_data + 2));
if (static_cast<unsigned int>(length + 4) > p_packet.m_length)
{
return false;
}
if (p_packet.m_data[1] == 0)
{
return false;
}
p_packet.m_data += 4;
p_packet.m_length -= 4;
if (p_packet.m_length < sizeof(key_struct))
{
return false;
}
const struct key_struct* key_data =
reinterpret_cast<const struct key_struct*>(p_packet.m_data);
if (key_data->type != 1 && key_data->type != 2)
{
// these aren't key types we are interested in
return false;
}
// Used to build HCCAPX here. However, it appears that cap2hccapx is
// significantly better than my crap. Maybe think of a way to isolate eapol
// and a beacon for each auth attempt? Otherwise its fine to just convert
// to hccapx at the end of the run.
p_packet.m_stats.increment_eapol();
return true;
}
================================================
FILE: pi_sniffer/src/protocols/eapol11.hpp
================================================
#ifndef EAPOL_HPP
#define EAPOL_HPP
class Packet;
class EAPOL
{
public:
EAPOL();
~EAPOL();
bool handle_packet(Packet& p_packet);
};
#endif //EAPOL_HPP
================================================
FILE: pi_sniffer/src/protocols/ieee80211.cpp
================================================
#include "ieee80211.hpp"
#include "packet.hpp"
#include "client.hpp"
#include "ap.hpp"
#include "probed_network.hpp"
#include "util/convert.hpp"
#include <tins/dot11/dot11_data.h>
#include <boost/cstdint.hpp>
#include <boost/foreach.hpp>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
IEEE80211::IEEE80211() :
m_snap(),
m_pcap_out()
{
}
IEEE80211::~IEEE80211()
{
}
bool IEEE80211::handle_packet(Packet& p_packet)
{
if (p_packet.m_length < 8)
{
return false;
}
switch (p_packet.m_data[0])
{
case 0x00:
do_association(p_packet);
break;
case 0x40:
do_probe_request(p_packet);
break;
case 0x50:
do_probe_response(p_packet);
break;
case 0x80:
do_beacon(p_packet);
break;
case 0x08:
do_data(p_packet);
break;
case 0x88:
do_qos(p_packet);
break;
default:
break;
}
return true;
}
AP* IEEE80211::get_ap(Packet& p_packet, std::size_t p_ssid_offset, int p_depth)
{
boost::uint64_t bssid_mac = (*reinterpret_cast<const boost::uint64_t*>(
p_packet.m_data + p_ssid_offset));
bssid_mac = (bssid_mac >> 16);
bssid_mac = (bssid_mac << 16);
bssid_mac = be64toh(bssid_mac);
if (bssid_mac == 0 && p_depth == 1)
{
return get_ap(p_packet, p_ssid_offset - 6, 0);
}
return p_packet.find_ap(bssid_mac);
}
Client* IEEE80211::get_client(Packet& p_packet, std::size_t p_src_offset, bool p_associated)
{
boost::uint64_t src_mac = (*reinterpret_cast<const boost::uint64_t*>(
p_packet.m_data + p_src_offset));
src_mac = (src_mac >> 16);
src_mac = (src_mac << 16);
src_mac = be64toh(src_mac);
if (src_mac == 0x0000ffffffffffffULL)
{
return NULL;
}
return p_packet.find_client(src_mac, p_associated);
}
void IEEE80211::do_probe_request(Packet& p_packet)
{
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
if (p_packet.m_length <= 26)
{
return;
}
// don't want to allocate a client for this since we only track associated
// clients. However, we want to track probbed networks, so extract the transmitter
boost::uint64_t src_mac = (*reinterpret_cast<const boost::uint64_t*>(
p_packet.m_data + 8));
src_mac = (src_mac >> 16);
src_mac = (src_mac << 16);
src_mac = be64toh(src_mac);
unsigned char* mac = reinterpret_cast<unsigned char*>(&src_mac);
std::string mac_address(printable_mac(mac, 6));
// TODO this should really reuse the tagged parameters loop
if (p_packet.m_data[24] == 0)
{
if (p_packet.m_length < static_cast<std::size_t>(24 + p_packet.m_data[25]))
{
return;
}
std::string ssid(reinterpret_cast<const char*>(p_packet.m_data + 26), p_packet.m_data[25]);
p_packet.add_probe_network(ssid, mac_address);
}
}
void IEEE80211::do_probe_response(Packet& p_packet)
{
do_beacon(p_packet);
}
void IEEE80211::do_beacon(Packet& p_packet)
{
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
AP* found = get_ap(p_packet, 14, 1);
p_packet.m_stats.increment_beacons();
if (found->get_beacon_parsed())
{
// let's only do this once
return;
}
if (p_packet.m_length < 36)
{
return;
}
const unsigned char* management_frame = p_packet.m_data + 24;
boost::uint16_t capabilities = *reinterpret_cast<const boost::uint16_t*>(management_frame + 10);
if ((capabilities & 0x0010) != 0)
{
found->set_encryption("WEP");
}
else
{
found->set_encryption("None");
p_packet.m_stats.increment_unencrypted();
}
boost::uint16_t tagged_length = p_packet.m_length - 36;
if (tagged_length == 0)
{
return;
}
bool found_ssid = false;
bool wpa = false;
bool wpa2 = false;
bool psk = false;
bool eap = false;
const unsigned char* tagged_data = management_frame + 12;
while (tagged_length > 2)
{
if (tagged_length < (tagged_data[1] + 2))
{
break;
}
boost::uint8_t tag = tagged_data[0];
boost::uint8_t length = tagged_data[1];
const unsigned char* value = tagged_data + 2;
// skip over this data
tagged_length -= (tagged_data[1] + 2);
tagged_data += (tagged_data[1] + 2);
switch (tag)
{
case 0:
if (found_ssid)
{
break;
}
found_ssid = true;
if (length != 0)
{
if (value[0] != 0)
{
std::string ssid(reinterpret_cast<const char*>(value), length);
found->set_ssid(ssid);
}
}
else
{
found->set_ssid("<Unknown>");
}
break;
case 3:
found->set_channel(value[0]);
break;
case 0x30: // RSN
{
if (length <= 8)
{
break;
}
// jump version and oui lead in
value += 6;
boost::uint16_t pairwise = *reinterpret_cast<const boost::uint16_t*>(value);
if (length <= (8 + (pairwise * 4)))
{
break;
}
value += 2;
for (boost::uint16_t pair_loop = pairwise ; pair_loop > 0; --pair_loop)
{
if (value[3] == 0x04)
{
wpa2 = true;
}
value += 4;
}
if (!wpa2)
{
wpa = true;
}
boost::uint16_t auth = *reinterpret_cast<const boost::uint16_t*>(value);
if (length <= (10 + (pairwise * 4) + (auth * 4)))
{
break;
}
value += 2;
for ( ; auth > 0; --auth)
{
if (value[3] == 0x02)
{
psk = true;
}
else if(value[3] == 1)
{
eap = true;
}
value += 4;
}
break;
}
case 0xdd:
if (length > 4 && memcmp(value, "\x00\x50\xf2", 3) == 0) // MS
{
switch(value[3])
{
case 1: // WPA IE
{
if (length <= 12)
{
break;
}
wpa = true;
// jump version and oui lead in
value += 10;
boost::uint16_t pairwise = *reinterpret_cast<const boost::uint16_t*>(value);
if (length <= (12 + (pairwise * 4)))
{
break;
}
value += 2;
for (boost::uint16_t pair_loop = pairwise; pair_loop > 0; --pair_loop)
{
value += 4;
}
boost::uint16_t auth = *reinterpret_cast<const boost::uint16_t*>(value);
if (length <= (14 + (pairwise * 4) + (auth * 4)))
{
break;
}
for ( ; auth > 0; --auth)
{
if (value[3] == 0x02)
{
psk = true;
}
else if (value[3] == 1)
{
eap = true;
}
value += 4;
}
break;
}
case 4: // wps
// skip over oui and type
value += 4;
length -= 4;
while (length > 4)
{
boost::uint16_t type = ntohs(*reinterpret_cast<const boost::uint16_t*>(value));
value += 2;
length -= 2;
boost::uint16_t inner_length = ntohs(*reinterpret_cast<const boost::uint16_t*>(value));
value += 2;
length -= 2;
if (inner_length > length)
{
break;
}
switch (type)
{
case 0x1011:
if (found->get_ssid().empty())
{
found->set_ssid(std::string(reinterpret_cast<const char*>(value), inner_length));
}
break;
case 0x1044:
if (*value == 0x02)
{
found->set_wps(true);
p_packet.m_stats.increment_wps();
}
break;
default:
break;
}
value += inner_length;
length -= inner_length;
}
break;
default:
break;
}
}
break;
default:
break;
}
}
// libtins wants a shot at the beacon... honestly we should just rewrite to use libtins
if (p_packet.m_current_router &&
p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))
{
try
{
boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));
if (tinsPacket.get() != NULL)
{
p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);
}
}
catch (const std::exception&)
{
}
}
found->set_beacon_parsed();
std::string encryption;
if (wpa)
{
encryption.append("WPA");
}
if (wpa2)
{
if (!encryption.empty())
{
encryption.push_back('/');
}
encryption.append("WPA2");
}
if (wpa || wpa2)
{
p_packet.m_stats.increment_wpa();
}
else if (found->get_encryption() == "WEP")
{
p_packet.m_stats.increment_wep();
}
if (psk)
{
encryption.append("-PSK");
}
else if (eap)
{
encryption.append("-EAP");
}
if (!encryption.empty())
{
found->set_encryption(encryption);
}
}
void IEEE80211::do_association(Packet& p_packet)
{
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
AP* found = get_ap(p_packet, 14);
if (p_packet.m_length < 36)
{
return;
}
const unsigned char* management_frame = p_packet.m_data + 24;
boost::uint16_t tagged_length = p_packet.m_length - 36;
if (tagged_length == 0)
{
return;
}
bool found_ssid = false;
const unsigned char* tagged_data = management_frame + 4;
while (tagged_length > 2)
{
if (tagged_length < (tagged_data[1] + 2))
{
break;
}
boost::uint8_t tag = tagged_data[0];
boost::uint8_t length = tagged_data[1];
const unsigned char* value = tagged_data + 2;
tagged_length -= (tagged_data[1] + 2);
tagged_data += (tagged_data[1] + 2);
switch (tag)
{
case 0:
if (found_ssid)
{
break;
}
found_ssid = true;
if (length != 0)
{
if (value[0] != 0)
{
std::string ssid(reinterpret_cast<const char*>(value), length);
found->set_ssid(ssid);
}
}
else
{
found->set_ssid("<Unknown>");
}
break;
case 3:
found->set_channel(value[0]);
break;
default:
break;
}
}
}
void IEEE80211::do_data(Packet& p_packet)
{
AP* found = NULL;
Client* client = NULL;
if ((p_packet.m_data[1] & 0x03) == 0x03)
{
found = get_ap(p_packet, 8);
client = get_client(p_packet, 22, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
p_packet.m_length -= 6;
p_packet.m_data += 6;
}
else if ((p_packet.m_data[1] & 0x02) == 2)
{
found = get_ap(p_packet, 8);
if (memcmp(p_packet.m_data + 10, p_packet.m_data + 16, 6) != 0)
{
client = get_client(p_packet, 14, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
}
else
{
p_packet.m_from_client = false;
}
}
else if((p_packet.m_data[1] & 0x01) == 1)
{
found = get_ap(p_packet, 2);
client = get_client(p_packet, 8, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
}
else
{
found = get_ap(p_packet, 14);
client = get_client(p_packet, 8, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
}
found->increment_data_packet();
p_packet.m_stats.increment_data_packets();
if (p_packet.m_length > 24)
{
handle_data(p_packet, 24);
}
}
void IEEE80211::do_qos(Packet& p_packet)
{
AP* found = NULL;
Client* client = NULL;
if ((p_packet.m_data[1] & 0x03) == 0x03)
{
found = get_ap(p_packet, 8);
client = get_client(p_packet, 22, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
p_packet.m_length -= 6;
p_packet.m_data += 6;
}
else if ((p_packet.m_data[1] & 0x02) == 2)
{
found = get_ap(p_packet, 8);
client = get_client(p_packet, 14, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = false;
}
else if ((p_packet.m_data[1] & 0x01) == 1)
{
found = get_ap(p_packet, 2);
client = get_client(p_packet, 8, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
}
else
{
found = get_ap(p_packet, 14);
client = get_client(p_packet, 8, true);
if (client == NULL)
{
return;
}
p_packet.m_from_client = true;
}
found->increment_data_packet();
p_packet.m_stats.increment_data_packets();
if (p_packet.m_length > 26)
{
handle_data(p_packet, 26);
}
}
void IEEE80211::handle_data(Packet& p_packet, boost::uint32_t p_increment)
{
// check if we have a snap header
if (memcmp(p_packet.m_data + p_increment, "\xaa\xaa\x03", 3) == 0)
{
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
// libtins wants a shot at the handshake... honestly we should just rewrite to use libtins
if (p_packet.m_current_router &&
p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))
{
try
{
boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));
if (tinsPacket.get() != NULL)
{
p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);
}
}
catch (const std::exception&)
{
}
}
p_packet.m_length -= p_increment;
p_packet.m_data += p_increment;
m_snap.handle_packet(p_packet);
}
else
{
// is this wep or wpa?
const std::string& encryption(p_packet.m_current_router->get_encryption());
if (encryption == "WEP")
{
p_packet.m_stats.increment_encrypted();
handle_wep(p_packet);
}
else if (!encryption.empty())
{
p_packet.m_stats.increment_encrypted();
handle_wpa(p_packet);
}
else if (p_packet.get_config().get_pcap())
{
// cleartext that isn't SNAP
m_pcap_out.add_packet(p_packet);
}
}
}
void IEEE80211::handle_wep(Packet& p_packet)
{
if (p_packet.m_current_router &&
p_packet.get_const_config().has_wep_key(p_packet.m_current_router->get_mac()))
{
try
{
boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));
if (tinsPacket.get() != NULL)
{
bool return_value = p_packet.get_config().m_wep_decrypter.decrypt(*tinsPacket);
if (return_value)
{
std::vector<unsigned char> decrypted = tinsPacket->serialize();
if (!decrypted.empty())
{
p_packet.m_stats.increment_decrypted();
p_packet.m_data = &decrypted[0];
p_packet.m_length = decrypted.size();
if (p_packet.get_const_config().get_pcap())
{
// write the decrypted version to the pcap
m_pcap_out.add_packet(p_packet);
}
if (memcmp(p_packet.m_data, "\xaa\xaa\x03\x00\x00", 5) == 0)
{
// we only really handle snap at this point
m_snap.handle_packet(p_packet);
}
return;
}
}
// decryption failed for some reason
p_packet.m_stats.increment_failed_decrypt();
}
}
catch (std::exception&)
{
}
}
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
}
void IEEE80211::handle_wpa(Packet& p_packet)
{
if (p_packet.m_current_router &&
p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))
{
try
{
boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));
if (tinsPacket.get() != NULL)
{
bool return_value = p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);
if (return_value)
{
std::vector<unsigned char> decrypted = tinsPacket->serialize();
if (!decrypted.empty())
{
p_packet.m_stats.increment_decrypted();
p_packet.m_data = &decrypted[0];
p_packet.m_length = decrypted.size();
if (p_packet.get_const_config().get_pcap())
{
// write the decrypted version to the pcap
m_pcap_out.add_packet(p_packet);
}
if (memcmp(p_packet.m_data, "\xaa\xaa\x03\x00\x00", 5) == 0)
{
// we only really handle snap at this point
m_snap.handle_packet(p_packet);
}
return;
}
}
// decryption failed for some reason
p_packet.m_stats.increment_failed_decrypt();
}
}
catch (std::exception&)
{
}
}
if (p_packet.get_const_config().get_pcap())
{
m_pcap_out.add_packet(p_packet);
}
}
================================================
FILE: pi_sniffer/src/protocols/ieee80211.hpp
================================================
#include <cstddef>
#include <boost/scoped_ptr.hpp>
#include <boost/cstdint.hpp>
#include "llcsnap.hpp"
#include "util/pcap_output.hpp"
class AP;
class Client;
class IEEE80211
{
public:
IEEE80211();
~IEEE80211();
bool handle_packet(Packet& p_packet);
private:
AP* get_ap(Packet& p_packet, std::size_t p_ssid_offset, int p_depth = 0);
Client* get_client(Packet& p_packet, std::size_t p_src_offset, bool p_associated);
void do_probe_request(Packet& p_packet);
void do_probe_response(Packet& p_packet);
void do_beacon(Packet& p_packet);
void do_data(Packet& p_packet);
void do_qos(Packet& p_packet);
void do_association(Packet& p_packet);
void handle_data(Packet& p_packet, boost::uint32_t p_increment);
void handle_wep(Packet& p_packet);
void handle_wpa(Packet& p_packet);
private:
LLCSNAP m_snap;
PcapOutput m_pcap_out;
};
================================================
FILE: pi_sniffer/src/protocols/llcsnap.cpp
================================================
#include "llcsnap.hpp"
#include <sstream>
#include <netinet/in.h>
#include "packet.hpp"
LLCSNAP::LLCSNAP() :
m_eapol()
{
}
LLCSNAP::~LLCSNAP()
{
}
bool LLCSNAP::handle_packet(Packet& p_packet)
{
if (p_packet.m_length < 8)
{
return false;
}
boost::uint16_t next_proto = *reinterpret_cast<const boost::uint16_t*>(p_packet.m_data + 6);
next_proto = ntohs(next_proto);
p_packet.m_data += 8;
p_packet.m_length -= 8;
if (next_proto == 0x888e)
{
m_eapol.handle_packet(p_packet);
}
return true;
}
================================================
FILE: pi_sniffer/src/protocols/llcsnap.hpp
================================================
#ifndef SNAP_HPP
#define SNAP_HPP
class Packet;
#include "eapol11.hpp"
class LLCSNAP
{
public:
LLCSNAP();
~LLCSNAP();
bool handle_packet(Packet& p_packet);
private:
EAPOL m_eapol;
};
#endif
================================================
FILE: pi_sniffer/src/stats.cpp
================================================
#include "stats.hpp"
Stats::Stats() :
m_accesslock(),
m_unecrypted(0),
m_wep(0),
m_wpa(0),
m_wps(0),
m_data(0),
m_encrypted(0),
m_decrypted(0),
m_failed_decrypt(0),
m_packets(0),
m_beacons(0),
m_eapol(0)
{
}
Stats::~Stats()
{
}
void Stats::increment_unencrypted()
{
++m_unecrypted;
}
boost::uint32_t Stats::get_unencrypted() const
{
return m_unecrypted;
}
void Stats::increment_wep()
{
++m_wep;
}
boost::uint32_t Stats::get_wep() const
{
return m_wep;
}
void Stats::increment_wpa()
{
++m_wpa;
}
boost::uint32_t Stats::get_wpa() const
{
return m_wpa;
}
void Stats::increment_wps()
{
++m_wps;
}
void Stats::increment_data_packets()
{
++m_data;
}
boost::uint32_t Stats::get_data_packets() const
{
return m_data;
}
void Stats::increment_packets()
{
++m_packets;
}
boost::uint32_t Stats::get_packets() const
{
return m_packets;
}
void Stats::increment_beacons()
{
++m_beacons;
}
boost::uint32_t Stats::get_beacons() const
{
return m_beacons;
}
void Stats::increment_encrypted()
{
++m_encrypted;
}
boost::uint32_t Stats::get_encrypted() const
{
return m_encrypted;
}
boost::uint32_t Stats::get_decrypted() const
{
return m_decrypted;
}
void Stats::increment_decrypted()
{
++m_decrypted;
}
boost::uint32_t Stats::get_failed_decrypt() const
{
return m_failed_decrypt;
}
void Stats::increment_failed_decrypt()
{
++m_failed_decrypt;
}
boost::uint32_t Stats::get_eapol() const
{
return m_eapol;
}
void Stats::increment_eapol()
{
++m_eapol;
}
================================================
FILE: pi_sniffer/src/stats.hpp
================================================
#ifndef STATS_HPP
#define STATS_HPP
#include <boost/thread.hpp>
#include <boost/cstdint.hpp>
/**
* Stats is a an object that is largely a wrapper simple counters. The information
* in stats is tracked to get a high level overview of what the engine has seen.
* Most useful in a overview type screen or shutdown stats.
*/
class Stats
{
public:
Stats();
~Stats();
void increment_unencrypted();
boost::uint32_t get_unencrypted() const;
void increment_wep();
boost::uint32_t get_wep() const;
void increment_wpa();
boost::uint32_t get_wpa() const;
void increment_wps();
void increment_data_packets();
boost::uint32_t get_data_packets() const;
void increment_packets();
boost::uint32_t get_packets() const;
void increment_beacons();
boost::uint32_t get_beacons() const;
void increment_probe_requests();
boost::uint32_t get_encrypted() const;
void increment_encrypted();
boost::uint32_t get_decrypted() const;
void increment_decrypted();
boost::uint32_t get_failed_decrypt() const;
void increment_failed_decrypt();
boost::uint32_t get_eapol() const;
void increment_eapol();
private:
Stats(const Stats& p_rhs);
Stats& operator=(const Stats& p_rhs);
private:
boost::mutex m_accesslock;
boost::uint32_t m_unecrypted;
boost::uint32_t m_wep;
boost::uint32_t m_wpa;
boost::uint32_t m_wps;
boost::uint32_t m_data;
boost::uint32_t m_encrypted;
boost::uint32_t m_decrypted;
boost::uint32_t m_failed_decrypt;
boost::uint32_t m_packets;
boost::uint32_t m_beacons;
boost::uint32_t m_eapol;
};
#endif
================================================
FILE: pi_sniffer/src/util/convert.cpp
================================================
#include "convert.hpp"
#include <string>
#include <cassert>
#include <stdexcept>
#include <cctype>
#include <algorithm>
#include <vector>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/address_v6.hpp>
#ifdef OS_WINDOWS
#include <stdlib.h>
#endif
namespace
{
const unsigned char k_hex[] =
{
'0','1','2','3','4','5','6','7',
'8','9','a','b','c','d','e','f'
};
const unsigned char k_hex_int[] =
{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
};
const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char reverse_table[128] =
{
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 64, 64, 64, 64, 64
};
}
std::string printable_mac(const unsigned char* p_data, std::size_t p_length, bool p_reverse)
{
std::string return_string;
for(std::size_t byte = 0; byte < p_length; ++byte)
{
if (p_reverse)
{
return_string.push_back(k_hex[p_data[byte] & 0x0F]);
return_string.push_back(k_hex[(p_data[byte] >> 4) & 0x0F]);
}
else
{
return_string.push_back(k_hex[(p_data[byte] >> 4) & 0x0F]);
return_string.push_back(k_hex[p_data[byte] & 0x0F]);
}
if(byte + 1 != p_length)
{
return_string.push_back(':');
}
}
if (p_reverse)
{
std::reverse(return_string.begin(), return_string.end());
}
return return_string;
}
boost::uint64_t string_mac_to_int(const std::string& p_mac)
{
std::vector<std::string> octets;
boost::algorithm::split(octets, p_mac, boost::is_any_of(":"));
if (octets.size() != 6)
{
throw std::runtime_error("Malformed MAC address");
}
boost::uint64_t return_value = 0;
BOOST_FOREACH(std::string& octet, octets)
{
unsigned int convert = 0;
std::stringstream in;
in << std::hex << octet;
in >> convert;
convert = (convert & 0xff);
return_value = (return_value << 8);
return_value |= (convert & 0xff);
}
return return_value;
}
Tins::HWAddress<6> int_mac_to_array(boost::uint64_t p_mac)
{
boost::array<unsigned char, 6> address;
for (int i = 5, j=0; i >= 0; --i, ++j)
{
address[i] = reinterpret_cast<unsigned char*>(&p_mac)[j];
}
Tins::HWAddress<6> return_value(address.c_array());
return return_value;
}
std::string string_to_hex(const std::string& p_mac1)
{
std::string p_mac(p_mac1);
std::string return_value;
unsigned char hex_value = 0;
if ((p_mac.size() % 2) != 0)
{
throw std::runtime_error("Hex strings must have both nibbles");
}
// replace the a-f values
for (std::size_t i = 0; i < p_mac.size(); ++i)
{
switch (p_mac[i])
{
case '0':
p_mac[i] = 0x00;
break;
case '1':
p_mac[i] = 0x01;
break;
case '2':
p_mac[i] = 0x02;
break;
case '3':
p_mac[i] = 0x03;
break;
case '4':
p_mac[i] = 0x04;
break;
case '5':
p_mac[i] = 0x05;
break;
case '6':
p_mac[i] = 0x06;
break;
case '7':
p_mac[i] = 0x07;
break;
case '8':
p_mac[i] = 0x08;
break;
case '9':
p_mac[i] = 0x09;
break;
case 'a':
p_mac[i] = 0x0a;
break;
case 'b':
p_mac[i] = 0x0b;
break;
case 'c':
p_mac[i] = 0x0c;
break;
case 'd':
p_mac[i] = 0x0d;
break;
case 'e':
p_mac[i] = 0x0e;
break;
case 'f':
p_mac[i] = 0x0f;
break;
default:
std::stringstream error;
error << "Non hex value in decrypt key: " << p_mac[i];
throw std::runtime_error(error.str());
}
if ((i % 2) == 0)
{
hex_value = p_mac[i];
hex_value = (hex_value << 4) & 0xf0;
}
else
{
hex_value |= (p_mac[i] & 0x0f);
return_value.push_back(hex_value);
hex_value = 0;
}
}
return return_value;
}
================================================
FILE: pi_sniffer/src/util/convert.hpp
================================================
#include <string>
#include <cstddef>
#include <boost/array.hpp>
#include <boost/cstdint.hpp>
#include <tins/hw_address.h>
std::string printable_mac(const unsigned char* p_data, std::size_t p_length, bool p_reverse = true);
boost::uint64_t string_mac_to_int(const std::string& p_mac);
Tins::HWAddress<6> int_mac_to_array(boost::uint64_t p_mac);
std::string string_to_hex(const std::string& p_hex);
================================================
FILE: pi_sniffer/src/util/kml_maker.cpp
================================================
#include "kml_maker.hpp"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <boost/foreach.hpp>
#include "ap.hpp"
namespace
{
std::string s_header("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\t<Document>\n");
std::string s_footer("\t\t</Folder>\n\t</Document>\n</kml>");
}
KML_Maker::KML_Maker() :
m_open(),
m_wep(),
m_wpa()
{
}
KML_Maker::~KML_Maker()
{
}
void KML_Maker::load_aps(boost::ptr_unordered_map<boost::uint64_t, AP>& p_ap)
{
for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator current_ap = p_ap.begin();
current_ap != p_ap.end(); ++current_ap)
{
if (current_ap->second->get_best_longitude() > 1.0 || current_ap->second->get_best_longitude() < -1.0)
{
if (current_ap->second->get_encryption() == "WEP")
{
m_wep[current_ap->first] = current_ap->second;
}
else if (current_ap->second->get_encryption().find("WPA") != std::string::npos)
{
m_wpa[current_ap->first] = current_ap->second;
}
else
{
m_open[current_ap->first] = current_ap->second;
}
}
}
}
std::string KML_Maker::write_color(const std::string& p_color) const
{
std::string return_value;
return_value.append("\t\t<Style id=\"");
return_value.append(p_color);
return_value.append("\">\n");
return_value.append("\t\t\t<IconStyle>\n");
return_value.append("\t\t\t\t<Icon><href>http://maps.google.com/mapfiles/ms/icons/");
return_value.append(p_color);
return_value.append("-dot.png</href></Icon>\n");
return_value.append("\t\t\t</IconStyle>\n\t\t</Style>\n");
return_value.append("\t\t<Folder>\n");
return return_value;
}
std::string KML_Maker::write_ap(const std::map<boost::uint64_t, AP*>& p_aps) const
{
std::string return_value;
for (std::map<boost::uint64_t, AP*>::const_iterator current_ap = p_aps.begin();
current_ap != p_aps.end(); ++current_ap)
{
return_value.append("\t\t\t<Placemark>\n\t\t\t\t<name><![CDATA[");
return_value.append(current_ap->second->get_ssid());
return_value.append("]]></name>\n\t\t\t\t<description>\n\t\t\t\t\t<![CDATA[BSSID: <b>");
return_value.append(current_ap->second->get_mac());
return_value.append("</b><br/>RSSI: <b>");
std::stringstream rssi;
rssi << static_cast<int>(current_ap->second->get_best_signal());
return_value.append(rssi.str());
return_value.append("</b><br/>Channel: <b>");
std::stringstream channel;
channel << (int)current_ap->second->get_channel();
return_value.append(channel.str());
return_value.append("</b><br/>Encryption: <b>");
return_value.append(current_ap->second->get_encryption());
return_value.append("</b><br/>First Seen: <b>");
const long int timestamp = current_ap->second->get_first_seen();
std::tm* t = std::localtime(×tamp);
std::stringstream firstTime;
firstTime << std::put_time(t, "%Y-%m-%d %I:%M:%S %p");
return_value.append(firstTime.str());
return_value.append("</b>]]>\n\t\t\t\t</description>\n");
return_value.append("\t\t\t\t<styleUrl>#");
if (current_ap->second->get_encryption() == "WEP")
{
return_value.append("pink");
}
else if (current_ap->second->get_encryption().find("WPA") != std::string::npos)
{
return_value.append("green");
}
else
{
return_value.append("blue");
}
return_value.append("</styleUrl>\n\t\t\t\t<Point>\n\t\t\t\t\t<coordinates>");
std::stringstream longitude;
longitude << current_ap->second->get_best_longitude();
return_value.append(longitude.str());
return_value.append(",");
std::stringstream latitude;
latitude << current_ap->second->get_best_latitude();
return_value.append(latitude.str());
return_value.append("</coordinates>\n");
return_value.append("\t\t\t\t</Point>\n\t\t\t</Placemark>\n");
}
return return_value;
}
void KML_Maker::write_all(const std::string& p_filename) const
{
write_open(p_filename);
write_wep(p_filename);
write_wpa(p_filename);
}
void KML_Maker::write_open(const std::string& p_filename) const
{
if (m_open.empty())
{
return;
}
std::string filename(p_filename + "_open.kml");
std::ofstream open_ap;
open_ap.open(filename);
if (!open_ap.is_open())
{
return;
}
open_ap << s_header;
open_ap << write_color("blue");
open_ap << "\t\t<name>" << filename << "</name>\n";
open_ap << write_ap(m_open);
open_ap << s_footer;
open_ap.close();
}
void KML_Maker::write_wep(const std::string& p_filename) const
{
if (m_wep.empty())
{
return;
}
std::string filename(p_filename + "_wep.kml");
std::ofstream wep_ap;
wep_ap.open(filename);
if (!wep_ap.is_open())
{
return;
}
wep_ap << s_header;
wep_ap << write_color("pink");
wep_ap << "\t\t<name>" << filename << "</name>\n";
wep_ap << write_ap(m_wep);
wep_ap << s_footer;
wep_ap.close();
}
void KML_Maker::write_wpa(const std::string& p_filename) const
{
if (m_wpa.empty())
{
return;
}
std::string filename(p_filename + "_wpa.kml");
std::ofstream wpa_ap;
wpa_ap.open(filename);
if (!wpa_ap.is_open())
{
return;
}
wpa_ap << s_header;
wpa_ap << write_color("green");
wpa_ap << "\t\t<name>" << filename << "</name>\n";
wpa_ap << write_ap(m_wpa);
wpa_ap << s_footer;
wpa_ap.close();
}
================================================
FILE: pi_sniffer/src/util/kml_maker.hpp
================================================
#ifndef KML_MAKER_HPP
#define KML_MAKER_HPP
#include <map>
#include <string>
#include <vector>
#include <boost/ptr_container/ptr_unordered_map.hpp>
class AP;
class KML_Maker
{
public:
KML_Maker();
~KML_Maker();
void load_aps(boost::ptr_unordered_map<boost::uint64_t, AP>& p_ap);
void write_all(const std::string& p_filename) const;
private:
void write_open(const std::string& p_filename) const;
void write_wep(const std::string& p_filename) const;
void write_wpa(const std::string& p_filename) const;
std::string write_color(const std::string& p_color) const;
std::string write_ap(const std::map<boost::uint64_t, AP*>& p_aps) const;
private:
std::map<boost::uint64_t, AP*> m_open;
std::map<boost::uint64_t, AP*> m_wep;
std::map<boost::uint64_t, AP*> m_wpa;
};
#endif
================================================
FILE: pi_sniffer/src/util/pcap_output.cpp
================================================
#include "pcap_output.hpp"
#include <boost/cstdint.hpp>
#include "../packet.hpp"
namespace
{
#pragma pack(push, 1)
struct pcap_header
{
boost::uint32_t magic_number;
boost::uint16_t version_major;
boost::uint16_t version_minor;
boost::int32_t thiszone;
boost::uint32_t sigfigs;
boost::uint32_t snaplen;
boost::uint32_t network;
};
struct packet_header
{
uint32_t ts_sec;
uint32_t ts_usec;
uint32_t incl_len;
uint32_t orig_len;
};
#pragma pack(pop)
}
PcapOutput::PcapOutput() :
m_pcapOut()
{
}
PcapOutput::~PcapOutput()
{
if (m_pcapOut.is_open())
{
m_pcapOut.close();
}
}
bool PcapOutput::create_header(const std::string& p_path)
{
pcap_header newHeader = {};
newHeader.magic_number = 0xa1b2c3d4;
newHeader.version_major = 2;
newHeader.version_minor = 4;
newHeader.thiszone = 0;
newHeader.sigfigs = 0;
newHeader.snaplen = 0xffff;
newHeader.network = 105; // ieee 802.11
// create the file
m_pcapOut.open(p_path, std::ofstream::binary);
m_pcapOut.write(reinterpret_cast<const char*>(&newHeader), sizeof(pcap_header));
return true;
}
void PcapOutput::add_packet(Packet& p_packet)
{
if (!m_pcapOut.is_open())
{
create_header(p_packet.get_const_config().get_output_path() + "pi_sniffer_" + p_packet.m_startTime + ".pcap");
}
packet_header packetHeader = {};
packetHeader.incl_len = p_packet.m_length;
packetHeader.orig_len = p_packet.m_length;
packetHeader.ts_sec = p_packet.m_time;
packetHeader.ts_usec = 0;
m_pcapOut.write(reinterpret_cast<const char*>(&packetHeader), sizeof(packet_header));
m_pcapOut.write(reinterpret_cast<const char*>(&p_packet.m_data[0]), p_packet.m_length);
m_pcapOut.flush();
}
================================================
FILE: pi_sniffer/src/util/pcap_output.hpp
================================================
#ifndef PCAP_OUTPUT_HPP
#define PCAP_OUTPUT_HPP
#include <string>
#include <fstream>
class Packet;
/*! Create PPI pcap file */
class PcapOutput
{
public:
PcapOutput();
~PcapOutput();
bool create_header(const std::string& p_path);
void add_packet(Packet& p_packet);
private:
std::ofstream m_pcapOut;
};
#endif
================================================
FILE: ui/display_handler.py
================================================
import socket
import time
import board
import busio
import re
import subprocess
from enum import Enum
import adafruit_ssd1306
from digitalio import DigitalInOut, Direction, Pull
from PIL import Image, ImageDraw, ImageFont
###
# This monolithic madness is the entire UI of pi sniffer. It communicates with
# the pi_sniffer engine over UDP and with Kismet over TCP. It issues various
# shell commands in order to interact with the system. It checks to see if the
# UI needs repainting every 0.1ish seconds. An enterprising individual might
# break this thing up.
###
###
# Hooray for globals!
###
# Create the I2C interface.
i2c = busio.I2C(board.SCL, board.SDA)
# Create a "display" that represents the OLED screen
disp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)
# Input pins
button_A = DigitalInOut(board.D5)
button_A.direction = Direction.INPUT
button_A.pull = Pull.UP
button_B = DigitalInOut(board.D6)
button_B.direction = Direction.INPUT
button_B.pull = Pull.UP
button_L = DigitalInOut(board.D27)
button_L.direction = Direction.INPUT
button_L.pull = Pull.UP
button_R = DigitalInOut(board.D23)
button_R.direction = Direction.INPUT
button_R.pull = Pull.UP
button_U = DigitalInOut(board.D17)
button_U.direction = Direction.INPUT
button_U.pull = Pull.UP
button_D = DigitalInOut(board.D22)
button_D.direction = Direction.INPUT
button_D.pull = Pull.UP
button_C = DigitalInOut(board.D4)
button_C.direction = Direction.INPUT
button_C.pull = Pull.UP
# views
status_view = 0
overview = 1
ap_view = 2
client_view = 3
antenna = 4
system_view = 5
gps_view = 6
lock_screen = 7
rotate = 8 # place holder
# ap view
selected_ap = 0
selected_ant = 0
selected_client = 0
# current view
current_view = status_view
# lock controls
locked = False
# do we need to update the view?
redraw = True
# last ap list
ap_list = []
clients_list = []
# last update time
last_update = 0
last_stats = None
# observed some curious behavior from kismet. After many hours it sometimes
# just stops sending data in the kismet packets. I'm 90% certain it isn't me.
# who knows. Poor man's solution: watchdog that restarts kismet
watch_dog = time.time()
# flush output every five minutes just in case of catestrophic error
flush_time = time.time()
# the font all text writing will use
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", 9)
# create a UDP socket to talk to pi_sniffer engine
backend_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
###
# Globals over. I am appropriately embarrassed.
###
##
# Issue a command to the pi sniffer UDP interface. Not all commands require
# responses (e.g. 'S\n' for shutdown does not require a response)
##
def pi_sniff_command(command, get_response):
data = None
pi_sniffer = subprocess.run(["ps", "-C", "pi_sniffer"], capture_output=True)
if (pi_sniffer.stdout.find(b"pi_sniffer") != -1):
backend_sock.sendto(command + b"\n", ("127.0.0.1", 1270))
if (get_response == True):
data = backend_sock.recvfrom(65535)[0]
return data
##
# Issue a generic kismet command (e.g. shutdown) and return
##
def do_kismet_command(command):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2501))
s.sendall(b"!0 " + command + b"\n")
s.close()
##
# Grab the current channel list, hop status, and current channel for the
# provided antenna (uuid defined in kismet.conf. wlan0 == 01 and wlan1 == 02)
##
def kismet_ant_info(uuid):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2501))
s.sendall(b"!0 ENABLE SOURCE uuid,channellist,hop,channel\n")
data = b""
try:
data = s.recv(1024)
s.close()
except:
pass
channel_info = re.search(b"SOURCE: " + uuid + b" ([0-9,]+) ([0-9]+) ([0-9]+)", data)
if channel_info == None:
return (b'',b'',b'')
else:
# channel list, hop status, current channel
return (channel_info.group(1), channel_info.group(2), channel_info.group(3))
##
# Set the channel of the provided antenna (uuid defined in kismet.conf). If
# the provided channel == "0" than switch to channel hopping.
##
def kismet_set_channel(uuid, channel):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2501))
if (channel == b"0"):
s.sendall(b"!0 HOPSOURCE " + uuid + b" HOP 3\n")
else:
s.sendall(b"!0 HOPSOURCE " + uuid + b" LOCK " + channel + b"\n")
s.close()
##
# Every 60 seconds check if kismet is still sending us data. I've observed it
# sending us empty packets for some reason... If we observe that behavior just
# restart it.
#
# Every 300 seconds tell pi_sniffer to flush output to disk.
##
def do_watchdog():
global watch_dog
global last_stats
global flush_time
if ((current_time - 60) > watch_dog):
watch_dog = current_time
overview_stats = pi_sniff_command(b"o", True)
if overview_stats != None:
stats = overview_stats.split(b",")
if last_stats == None:
last_stats = stats[5]
elif last_stats == stats[5]:
# we are either getting no data or kismet is behaving odd.
# knock it over and set it back up
last_stats = None
kismet = subprocess.run(["ps", "-C", "kismet_server"], capture_output=True)
if (kismet.stdout.find(b"kismet_server") != -1):
do_kismet_command(b"SHUTDOWN")
subprocess.run(["airmon-ng", "stop", "wlan0mon"])
subprocess.run(["airmon-ng", "stop", "wlan1mon"])
time.sleep(3)
subprocess.Popen(["kismet_server", "-f", "/home/pi/kismet.conf", "-n", "--daemonize"])
else:
last_stats = stats[5]
# let's check if we should flush output too
if ((current_time - 300) > flush_time):
flush_time = current_time
# only send the command if it's running
pi_sniffer = subprocess.run(["ps", "-C", "pi_sniffer"], capture_output=True)
if (pi_sniffer.stdout.find(b"pi_sniffer") != -1):
pi_sniff_command(b"f", False)
###
# Have the client attempt to rotate to the next screen
###
def check_view():
global redraw
global current_view
global selected_ap
global selected_ant
global selected_client
# Right joystick controls screen movement
if not button_R.value:
redraw = True
# reset screen specific items
# todo move this until ap_view
selected_ap = 0
selected_ant = 0
selected_client = 0
# move to the next screen
current_view = current_view + 1
current_view = current_view % rotate
# Left joystick controls screen movement too
elif not button_L.value:
redraw = True
# reset screen specific items
# todo move this until ap_view
selected_ap = 0
selected_ant = 0
selected_client = 0
if current_view == 0:
current_view = lock_screen
else:
current_view = current_view - 1
##
# Populate the start/status view
##
def do_status_view():
global redraw
if not button_A.value and not button_B.value:
# attempt a clean shutdown
pi_sniff_command(b"s", False)
time.sleep(5)
subprocess.run(["shutdown", "-h", "now"])
return False
elif not button_B.value:
# start kismet and pi sniffer
kismet = subprocess.run(["ps", "-C", "kismet_server"], capture_output=True)
if (kismet.stdout.find(b"kismet_server") == -1):
redraw = True
subprocess.Popen(["kismet_server", "-f", "/home/pi/kismet.conf", "-n", "--daemonize"])
time.sleep(3) # give it a second to get established
pi_sniffer = subprocess.run(["ps", "-C", "pi_sniffer"], capture_output=True)
if (pi_sniffer.stdout.find(b"pi_sniffer") == -1):
redraw = True
subprocess.Popen(["/home/pi/pi_sniffer/build/pi_sniffer", "-c", "/home/pi/pi_sniffer/pi_sniffer.conf", "-k", "127.0.0.1", "-p", "3501"])
elif not button_A.value:
# shutdown kismet and pi sniffer
redraw = True
pi_sniff_command(b"s", False)
kismet = subprocess.run(["ps", "-C", "kismet_server"], capture_output=True)
if (kismet.stdout.find(b"kismet_server") != -1):
do_kismet_command(b"SHUTDOWN")
subprocess.run(["airmon-ng", "stop", "wlan0mon"])
subprocess.run(["airmon-ng", "stop", "wlan1mon"])
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-12,0), "Status", fill=0)
kismet = subprocess.run(["ps", "-C", "kismet_server"], capture_output=True)
if (kismet.stdout.find(b"kismet_server") != -1):
draw.text((0,10), "Kismet: Running", font=font, fill=1)
else:
draw.text((0,10), "Kismet: Stopped", font=font, fill=1)
pi_sniffer = subprocess.run(["ps", "-C", "pi_sniffer"], capture_output=True)
if (pi_sniffer.stdout.find(b"pi_sniffer") != -1):
draw.text((0,20), "PiSniff: Running", font=font, fill=1)
else:
draw.text((0,20), "PiSniff: Stopped", font=font, fill=1)
wlan0mon = subprocess.run(["ifconfig", "wlan0mon"], capture_output=True)
if (wlan0mon.stdout.find(b"RUNNING,PROMISC") != -1):
draw.text((0,30), "wlan0mon: Up", font=font, fill=1)
else:
draw.text((0,30), "wlan0mon: Down", font=font, fill=1)
wlan1mon = subprocess.run(["ifconfig", "wlan1mon"], capture_output=True)
if (wlan1mon.stdout.find(b"RUNNING,PROMISC") != -1):
draw.text((0,40), "wlan1mon: Up", font=font, fill=1)
else:
draw.text((0,40), "wlan1mon: Down", font=font, fill=1)
gps_found = subprocess.run(["ls", "/dev/ttyACM0"], capture_output=True)
if (len(gps_found.stdout) > 0):
draw.text((0,50), "GPS: Available", font=font, fill=1)
else:
draw.text((0,50), "GPS: Not Found", font=font, fill=1)
return True
##
# Populate the overview screen
##
def do_overview():
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-16,0), "Overview", fill=0)
draw.line((width/2,10,width/2, height), fill=1)
overview_stats = pi_sniff_command(b"o", True)
if overview_stats != None:
stats = overview_stats.split(b",")
draw.text((0,10), "Time: " + stats[0].decode("utf-8"), font=font, fill=1)
draw.text((0,20), "APs: " + stats[1].decode("utf-8"), font=font, fill=1)
draw.text((0,30), "Open: " + stats[2].decode("utf-8"), font=font, fill=1)
draw.text((0,40), "WEP: " + stats[3].decode("utf-8"), font=font, fill=1)
draw.text((0,50), "WPA: " + stats[4].decode("utf-8"), font=font, fill=1)
draw.text((width/2+2,10), "Pkts: " + stats[5].decode("utf-8"), font=font, fill=1)
draw.text((width/2+2,20), "Bcns: " + stats[6].decode("utf-8"), font=font, fill=1)
draw.text((width/2+2,30), "Data: " + stats[7].decode("utf-8"), font=font, fill=1)
draw.text((width/2+2,40), "Enc: " + stats[8].decode("utf-8"), font=font, fill=1)
draw.text((width/2+2,50), "EAPOL: " + stats[9].decode("utf-8"), font=font, fill=1)
##
# Handle antenna view and input
##
def do_ant_view():
global redraw
global selected_ant
if not button_D.value: # down arrow
if (selected_ant < 2):
redraw = True
selected_ant = selected_ant + 1
elif not button_U.value: # up arrow
if (selected_ant > 0):
selected_ant = selected_ant - 1
redraw = True
elif not button_B.value and selected_ant != 0:
if selected_ant == 1:
wlan0mon = subprocess.run(["ifconfig", "wlan0mon"], capture_output=True)
if (wlan0mon.stdout.find(b"RUNNING,PROMISC") == -1):
# if the antenna doesn't exist do nothing
return
uid = b"00000000-0000-0000-0000-000000000001"
elif selected_ant == 2:
wlan1mon = subprocess.run(["ifconfig", "wlan1mon"], capture_output=True)
if (wlan1mon.stdout.find(b"RUNNING,PROMISC") == -1):
# if the antenna doesn't exist do nothing
return
uid = b"00000000-0000-0000-0000-000000000002"
else:
#ignore
return
(channellist, hopping, channel) = kismet_ant_info(uid)
channels = channellist.split(b",")
if (len(channels) > 0):
if hopping != b"0":
kismet_set_channel(uid, channels[0])
else:
current = channels.index(channel)
current = current + 1
if (current >= len(channels)):
kismet_set_channel(uid, b"0")
else:
kismet_set_channel(uid, channels[current])
# kismet needs a little before we slam it with more requests
time.sleep(0.3)
redraw = True
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-8,0), "Antenna", fill=0)
draw.line((width/2,10,width/2, height), fill=1)
if (selected_ant == 1):
draw.rectangle((0,10,width/2,20),outline=1,fill=1)
draw.text((0,10), "wlan0mon", font=font, fill=0)
wlan0mon = subprocess.run(["ifconfig", "wlan0mon"], capture_output=True)
if (wlan0mon.stdout.find(b"RUNNING,PROMISC") == -1):
draw.text((width/2+2,10), "Disabled", font=font,fill=1)
else:
(channellist, hopping, channel) = kismet_ant_info(b"00000000-0000-0000-0000-000000000001")
if hopping == b'':
draw.text((width/2+2,10), "Channel:\nTransitioning", font=font,fill=1)
elif hopping != b"0":
draw.text((width/2+2,10), "Channel:\nHopping", font=font,fill=1)
else:
draw.text((width/2+2,10), "Channel:\n" + channel.decode("utf-8"), font=font, fill=1)
else:
draw.text((0,10), "wlan0mon", font=font, fill=1)
if (selected_ant == 2):
draw.rectangle((0,20,width/2,30),outline=1,fill=1)
draw.text((0,20), "wlan1mon", font=font, fill=0)
wlan1mon = subprocess.run(["ifconfig", "wlan1mon"], capture_output=True)
if (wlan1mon.stdout.find(b"RUNNING,PROMISC") == -1):
draw.text((width/2+2,10), "Disabled", font=font,fill=1)
else:
(channellist, hopping, channel) = kismet_ant_info(b"00000000-0000-0000-0000-000000000002")
if hopping == b'':
draw.text((width/2+2,10), "Channel:\nTransitioning", font=font,fill=1)
elif hopping != b"0":
draw.text((width/2+2,10), "Channel:\nHopping", font=font,fill=1)
else:
draw.text((width/2+2,10), "Channel:\n" + channel.decode("utf-8"), font=font, fill=1)
else:
draw.text((0,20), "wlan1mon", font=font, fill=1)
##
# Draw the system view
##
def do_system_view():
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-36,0), "System Status", fill=0)
cpu = subprocess.run(["top", "-b", "-n", "1"], capture_output=True)
result = re.search(b"%Cpu\(s\):[ ]+[0-9\.]+[ ]+us,[ ]+[0-9\.]+[ ]+sy,[ ]+[0-9\.]+[ ]+ni,[ ]+([0-9\.]+)[ ]+id", cpu.stdout)
if (result == None):
draw.text((0,10), "CPU: Unknown%", font=font, fill=1)
else:
value = 100 - int(float(result.group(1)))
draw.text((0,10), "CPU: " + str(value) + "%", font=font, fill=1)
mem = subprocess.run(["vmstat", "-s"], capture_output=True)
total_mem = re.search(b"([0-9]+) K total memory\n", mem.stdout)
total_free= re.search(b"([0-9]+) K free memory\n", mem.stdout)
if (total_mem == None or total_free == None):
draw.text((0,20), "Memory: Unknown%", font=font, fill=1)
else:
value = 100 - ((float(total_free.group(1)) / float(total_mem.group(1))) *100)
draw.text((0,20), "Memory: " + str(int(value)) + "%", font=font, fill=1)
disk = subprocess.run(["df"], capture_output=True)
disk_usage = re.search(b"/dev/root[ ]+[A-Z0-9\.]+[ ]+[A-Z0-9\.]+[ ]+[A-Z0-9\.]+[ ]+([0-9]+)% /", disk.stdout)
if (disk_usage == None):
draw.text((0,30), "Disk: Unknown%", font=font, fill=1)
else:
draw.text((0,30), "Disk: " + disk_usage.group(1).decode("utf-8") + "%", font=font, fill=1)
temp = subprocess.run(["vcgencmd", "measure_temp"], capture_output=True)
temp_C = re.search(b"temp=(.*)", temp.stdout)
if (temp_C == None):
draw.text((0,40), "Temp: Unknown", font=font, fill=1)
else:
draw.text((0,40), "Temp: " + temp_C.group(1).decode("utf-8"), font=font, fill=1)
##
# Populate the GPS view
##
def do_gps_view():
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-28,0), "GPS Status", fill=0)
gps_found = subprocess.run(["ls", "/dev/ttyACM0"], capture_output=True)
if (len(gps_found.stdout) == 0):
draw.text((0,10), "Hardware: Not Found", font=font, fill=1)
else:
draw.text((0,10), "Hardware: Available", font=font, fill=1)
# does gpsd see the device?
gps_found = subprocess.run(["gpspipe", "-w", "-n", "2"], capture_output=True)
if (gps_found.stdout.find(b'"devices":[]') != -1):
draw.text((0,20), "Hardware: Not Recognized", font=font, fill=1)
else:
draw.text((0,20), "Hardware: Recognized", font=font, fill=1)
# grab lat long
gps_found = subprocess.run(["gpspipe", "-w", "-n", "4"], capture_output=True)
result = re.search(b'"lat":([0-9\.-]+),"lon":([0-9\.-]+),', gps_found.stdout)
if (result == None):
draw.text((0,30), "No sync", font=font, fill=1)
else:
draw.text((0,30), "Lat: " + result.group(1).decode("utf-8"), font=font, fill=1)
draw.text((0,40), "Lon: " + result.group(2).decode("utf-8"), font=font, fill=1)
##
# Populate the client view and handle user input
##
def do_client_view():
global redraw
global selected_client
global clients_list
if not button_D.value: # down arrow
if (selected_client < len(clients_list)):
redraw = True
selected_client = selected_client + 1
elif not button_U.value: # up arrow
if (selected_client > 0):
selected_client = selected_client - 1
redraw = True
elif not button_B.value:
if (selected_client > 0):
# grab the client's bssid
data = pi_sniff_command(b"c" + clients_list[selected_client - 1], True)
if data != None:
split_info = data.split(b",")
subprocess.run(["aireplay-ng", "-0", "1", "-a", split_info[1].decode("utf-8"), "-c", clients_list[selected_client - 1].decode("utf-8"), "wlan0mon"])
if redraw == True and selected_client == 0:
# get the list from the back end
clients = pi_sniff_command(b"c", True)
if (clients != None):
clients_list = clients.splitlines()
# trim the \n\n
clients_list = clients_list[:len(clients_list)-1]
if redraw == True:
# divide screen
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-30,0), "Client View", fill=0)
draw.line((width/2+22,10,width/2+22, height), fill=1)
if selected_client > 3:
i = selected_client - 4
else:
i = 0
location = 0
while (location < 5 and i < len(clients_list)):
if selected_client == (i+1):
draw.rectangle((0,(location*10)+10,width/2+22,(location*10)+20),outline=1,fill=1)
draw.text((0,(location*10)+10), clients_list[i].decode("utf-8"), font=font, fill=0)
data = pi_sniff_command(b"c" + clients_list[i], True)
if (data != None):
split_info = data.split(b",")
draw.text((width/2+22,10), split_info[1].decode("utf-8")[:9], font=font,fill=1)
draw.text((width/2+22,20), split_info[1].decode("utf-8")[9:], font=font,fill=1)
draw.text((width/2+22,30), "Sig: " + split_info[0].decode("utf-8"), font=font,fill=1)
else:
draw.text((0,(location*10)+10), clients_list[i].decode("utf-8"), font=font, fill=255)
i = i + 1
location = location + 1
def do_ap_view():
global redraw
global selected_ap
global ap_list
if not button_D.value: # down arrow
if (selected_ap < len(ap_list)):
redraw = True
selected_ap = selected_ap + 1
elif not button_U.value: # up arrow
if (selected_ap > 0):
selected_ap = selected_ap - 1
redraw = True
elif redraw == True and selected_ap == 0:
# get the list from the back end
access_points = pi_sniff_command(b"l", True)
if (access_points != None):
ap_list = access_points.splitlines()
ap_list = ap_list[:len(ap_list)-1]
if redraw == True:
# divide screen
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-18,0), "Live View", fill=0)
draw.line((width/2,10,width/2, height), fill=1)
# this supports forever scrolling... I hope
if selected_ap > 3:
i = selected_ap - 4
else:
i = 0
location = 0
while (location < 5 and i < len(ap_list)):
ap = ap_list[i].split(b",")
if (len(ap[0]) > 11):
shorten = ap[0][:8]
shorten = shorten + b"..."
ap[0] = shorten
if selected_ap == (i+1):
draw.rectangle((0,(location*10)+10,width/2,(location*10)+20),outline=1,fill=1)
draw.text((0,(location*10)+10), ap[0].decode("utf-8"), font=font, fill=0)
data = pi_sniff_command(b"r" + ap[1], True)
if (data != None):
split_info = data.split(b",")
draw.text((width/2+2,10), ap[1].decode("utf-8")[:9], font=font,fill=1)
draw.text((width/2+2,20), ap[1].decode("utf-8")[9:], font=font,fill=1)
draw.text((width/2+2,30), "Ch: " + split_info[0].decode("utf-8"),font=font, fill=1)
draw.text((width/2+2,40), split_info[1].decode("utf-8"), font=font,fill=1)
draw.text((width/2+2,50), "Sig: " + split_info[2].decode("utf-8"),font=font, fill=1)
draw.text((width/2+2,60), "Clnts: " + split_info[3].decode("utf-8"),font=font, fill=1)
else:
draw.text((0,(location*10)+10), ap[0].decode("utf-8"), font=font, fill=255)
i = i + 1
location = location + 1
##
# Handle the lock screen drawing and locking input (unlocked handled elsewhere)
##
def do_lock_screen():
global redraw
global locked
if not button_B.value: # button 6
locked = True
redraw = True
if redraw == True:
draw.rectangle((0,0,width,10),outline=1,fill=1)
draw.text(((width/2)-26,0), "Lock Status", fill=0)
if (locked == True):
draw.text((0,10), "Locked", font=font, fill=1)
else:
draw.text((0,10), "Unlocked", font=font, fill=1)
# ensure the echo is disabled on the gps tty. Really annoying this needs to be done.
subprocess.run(["stty", "-F", "/dev/ttyACM0", "-echo"])
# kill hdmi. power saving.
subprocess.run(["/usr/bin/tvservice", "-o"])
# loop until the user hits the break
# Clear display.
disp.fill(0)
disp.show()
while True:
if locked == True:
# the user can lock the display in the lock screen. If they have, don't
# do any other UI processing. We will have to still do the watch dog
# logic though
if not button_A.value and not button_U.value:
locked = False
redraw = True
else:
current_time = time.time()
if ((current_time - 6) > last_update):
redraw = True
do_watchdog()
time.sleep(0.1)
continue
# check if the user is changing the view
check_view()
# see if we should be refreshing
if redraw == False:
current_time = time.time()
if ((current_time - 6) > last_update):
redraw = True
# while we have current time let's kick the dog
do_watchdog()
# we might draw! Create a blank canvas
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, width, height), outline=0, fill=0)
# which view to draw to the screen
if (current_view == status_view):
if (do_status_view() == False):
# user has requested shutdown
break
elif (current_view == overview):
do_overview()
elif (current_view == antenna):
do_ant_view()
elif (current_view == system_view):
do_system_view()
elif (current_view == gps_view):
do_gps_view()
elif (current_view == client_view):
do_client_view()
elif (current_view == ap_view):
do_ap_view()
elif (current_view == lock_screen):
do_lock_screen()
else:
print("oh no! Why are we here?")
if (redraw == True):
last_update = time.time()
disp.image(image)
disp.show()
redraw = False
time.sleep(0.1)
disp.fill(0)
disp.show()
gitextract_gsji9l86/
├── .gitignore
├── LICENSE
├── README.md
├── configs/
│ ├── kismet.conf
│ ├── ntp.conf
│ └── rc.local
├── image_gen/
│ └── create_image.sh
├── pi_sniffer/
│ ├── CMakeLists.txt
│ ├── pi_sniffer.conf
│ └── src/
│ ├── ap.cpp
│ ├── ap.hpp
│ ├── client.cpp
│ ├── client.hpp
│ ├── configuration.cpp
│ ├── configuration.hpp
│ ├── input/
│ │ ├── kismet_drone.cpp
│ │ ├── kismet_drone.hpp
│ │ ├── pcap.cpp
│ │ └── pcap.hpp
│ ├── main.cpp
│ ├── packet.cpp
│ ├── packet.hpp
│ ├── probed_network.cpp
│ ├── probed_network.hpp
│ ├── protocols/
│ │ ├── eapol11.cpp
│ │ ├── eapol11.hpp
│ │ ├── ieee80211.cpp
│ │ ├── ieee80211.hpp
│ │ ├── llcsnap.cpp
│ │ └── llcsnap.hpp
│ ├── stats.cpp
│ ├── stats.hpp
│ └── util/
│ ├── convert.cpp
│ ├── convert.hpp
│ ├── kml_maker.cpp
│ ├── kml_maker.hpp
│ ├── pcap_output.cpp
│ └── pcap_output.hpp
└── ui/
└── display_handler.py
SYMBOL INDEX (93 symbols across 22 files)
FILE: pi_sniffer/src/ap.hpp
class AP (line 16) | class AP
FILE: pi_sniffer/src/client.hpp
class Client (line 14) | class Client
FILE: pi_sniffer/src/configuration.hpp
type pugi (line 11) | namespace pugi
type xml_node (line 13) | struct xml_node
class Configuration (line 24) | class Configuration
method get_pcap (line 39) | bool get_pcap() const
method get_wigle (line 44) | bool get_wigle() const
method get_kml (line 49) | bool get_kml() const
method get_client_csv (line 54) | bool get_client_csv() const
method get_probe_csv (line 59) | bool get_probe_csv() const
method get_ap_clients_csv (line 64) | bool get_ap_clients_csv() const
FILE: pi_sniffer/src/input/kismet_drone.cpp
type drone_trans_double (line 16) | struct drone_trans_double
type ieee_double_t (line 24) | struct ieee_double_t
function double_conversion_drone (line 33) | void double_conversion_drone(double& x, drone_trans_double* y)
FILE: pi_sniffer/src/input/kismet_drone.hpp
class Packet (line 8) | class Packet
class KismetDrone (line 10) | class KismetDrone
FILE: pi_sniffer/src/input/pcap.cpp
type pcap_header (line 13) | struct pcap_header
type packet_header (line 24) | struct packet_header
type ppi_packetheader (line 32) | struct ppi_packetheader
type ppi_fieldheader (line 40) | struct ppi_fieldheader
type gps_fields (line 46) | struct gps_fields
type radiotap_header (line 58) | struct radiotap_header
type ppi_common (line 66) | struct ppi_common
function fixed_3_7_to_flt (line 82) | double fixed_3_7_to_flt(boost::uint32_t in)
function fixed_6_4_to_flt (line 88) | double fixed_6_4_to_flt(boost::uint32_t in)
type pcap_header (line 119) | struct pcap_header
type pcap_header (line 119) | struct pcap_header
type packet_header (line 147) | struct packet_header
type packet_header (line 147) | struct packet_header
type radiotap_header (line 177) | struct radiotap_header
type radiotap_header (line 177) | struct radiotap_header
type ppi_packetheader (line 238) | struct ppi_packetheader
type ppi_packetheader (line 238) | struct ppi_packetheader
type ppi_fieldheader (line 255) | struct ppi_fieldheader
type ppi_fieldheader (line 255) | struct ppi_fieldheader
type gps_fields (line 259) | struct gps_fields
type gps_fields (line 259) | struct gps_fields
type ppi_fieldheader (line 270) | struct ppi_fieldheader
type ppi_common (line 279) | struct ppi_common
type ppi_common (line 279) | struct ppi_common
FILE: pi_sniffer/src/input/pcap.hpp
class Packet (line 9) | class Packet
class PCAP (line 11) | class PCAP
FILE: pi_sniffer/src/main.cpp
function parseCommandLine (line 16) | bool parseCommandLine(int p_argCount, char* p_argArray[],
function fileThread (line 83) | void fileThread(Packet& p_packet, const std::string& p_file)
function protocolThread (line 97) | void protocolThread(Packet& p_packet, const std::string& p_kismet_addres...
function main (line 128) | int main(int p_argCount, char* p_argArray[])
FILE: pi_sniffer/src/packet.cpp
function Configuration (line 55) | const Configuration& Packet::get_const_config() const
function Configuration (line 60) | Configuration& Packet::get_config()
function AP (line 123) | AP* Packet::find_ap(boost::uint64_t p_mac)
function Client (line 169) | Client* Packet::find_client(boost::uint64_t p_src_mac, bool p_associated)
FILE: pi_sniffer/src/packet.hpp
class Client (line 14) | class Client
class AP (line 15) | class AP
class Probed_Network (line 16) | class Probed_Network
class Packet (line 31) | class Packet
FILE: pi_sniffer/src/probed_network.hpp
class Probed_Network (line 13) | class Probed_Network
method get_clients_count (line 27) | std::size_t get_clients_count() const
FILE: pi_sniffer/src/protocols/eapol11.cpp
type key_struct (line 12) | struct key_struct
type key_struct (line 67) | struct key_struct
type key_struct (line 68) | struct key_struct
FILE: pi_sniffer/src/protocols/eapol11.hpp
class Packet (line 4) | class Packet
class EAPOL (line 6) | class EAPOL
FILE: pi_sniffer/src/protocols/ieee80211.cpp
function AP (line 64) | AP* IEEE80211::get_ap(Packet& p_packet, std::size_t p_ssid_offset, int p...
function Client (line 81) | Client* IEEE80211::get_client(Packet& p_packet, std::size_t p_src_offset...
FILE: pi_sniffer/src/protocols/ieee80211.hpp
class AP (line 8) | class AP
class Client (line 9) | class Client
class IEEE80211 (line 11) | class IEEE80211
FILE: pi_sniffer/src/protocols/llcsnap.hpp
class Packet (line 4) | class Packet
class LLCSNAP (line 8) | class LLCSNAP
FILE: pi_sniffer/src/stats.hpp
class Stats (line 12) | class Stats
FILE: pi_sniffer/src/util/convert.cpp
function printable_mac (line 58) | std::string printable_mac(const unsigned char* p_data, std::size_t p_len...
function string_mac_to_int (line 89) | boost::uint64_t string_mac_to_int(const std::string& p_mac)
function int_mac_to_array (line 114) | Tins::HWAddress<6> int_mac_to_array(boost::uint64_t p_mac)
function string_to_hex (line 126) | std::string string_to_hex(const std::string& p_mac1)
FILE: pi_sniffer/src/util/kml_maker.hpp
class AP (line 9) | class AP
class KML_Maker (line 11) | class KML_Maker
FILE: pi_sniffer/src/util/pcap_output.cpp
type pcap_header (line 10) | struct pcap_header
type packet_header (line 21) | struct packet_header
FILE: pi_sniffer/src/util/pcap_output.hpp
class Packet (line 7) | class Packet
class PcapOutput (line 10) | class PcapOutput
FILE: ui/display_handler.py
function pi_sniff_command (line 114) | def pi_sniff_command(command, get_response):
function do_kismet_command (line 128) | def do_kismet_command(command):
function kismet_ant_info (line 138) | def kismet_ant_info(uuid):
function kismet_set_channel (line 161) | def kismet_set_channel(uuid, channel):
function do_watchdog (line 178) | def do_watchdog():
function check_view (line 216) | def check_view():
function do_status_view (line 255) | def do_status_view():
function do_overview (line 327) | def do_overview():
function do_ant_view (line 349) | def do_ant_view():
function do_system_view (line 440) | def do_system_view():
function do_gps_view (line 479) | def do_gps_view():
function do_client_view (line 509) | def do_client_view():
function do_ap_view (line 566) | def do_ap_view():
function do_lock_screen (line 629) | def do_lock_screen():
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (165K chars).
[
{
"path": ".gitignore",
"chars": 463,
"preview": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to u"
},
{
"path": "LICENSE",
"chars": 637,
"preview": "This file is part of Pi Sniffer.\n\nPi Sniffer is free software: you can redistribute it and/or modify\nit under the terms "
},
{
"path": "README.md",
"chars": 3939,
"preview": "# Pi Sniffer\n\nPi Sniffer is a Wi-Fi sniffer built on the Raspberry Pi Zero W. While there are many excellent sniffing pl"
},
{
"path": "configs/kismet.conf",
"chars": 4959,
"preview": "# Version of Kismet config\nversion=2009-newcore\n\n# Do we process the contents of data frames? If this is enabled, data\n"
},
{
"path": "configs/ntp.conf",
"chars": 2368,
"preview": "# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help\n\ndriftfile /var/lib/ntp/ntp.drift\n\n# Leap seconds defi"
},
{
"path": "configs/rc.local",
"chars": 472,
"preview": "#!/bin/sh -e\n#\n# rc.local\n#\n# This script is executed at the end of each multiuser runlevel.\n# Make sure that the script"
},
{
"path": "image_gen/create_image.sh",
"chars": 6628,
"preview": "#!/usr/bin/env bash\n# based on: https://wiki.debian.org/RaspberryPi/qemu-user-static\n# and https://z4ziggy.wordpress.com"
},
{
"path": "pi_sniffer/CMakeLists.txt",
"chars": 1495,
"preview": "cmake_minimum_required(VERSION 2.8)\n\n# Options\noption(debug \"Build with debug flags.\" Off)\n\nset(PROJECT_NAME pi_sniffer)"
},
{
"path": "pi_sniffer/pi_sniffer.conf",
"chars": 552,
"preview": "<pi_sniffer>\n <wifidecrypt>\n <key type=\"wep\" bssid=\"00:12:bf:12:32:29\" key=\"1f1f1f1f1f\" />\n <key type=\""
},
{
"path": "pi_sniffer/src/ap.cpp",
"chars": 4007,
"preview": "#include \"ap.hpp\"\n#include \"util/convert.hpp\"\n\nAP::AP() :\n m_bssid(),\n m_lastseen(0),\n m_firstseen(0),\n m_la"
},
{
"path": "pi_sniffer/src/ap.hpp",
"chars": 3308,
"preview": "#ifndef AP_HPP\n#define AP_HPP\n\n#include <string>\n#include <boost/cstdint.hpp>\n#include <boost/thread/mutex.hpp>\n\n/*!\n * "
},
{
"path": "pi_sniffer/src/client.cpp",
"chars": 2960,
"preview": "#include \"client.hpp\"\n#include \"util/convert.hpp\"\n\nClient::Client() :\n m_lastseen(0),\n m_firstseen(0),\n m_assoc"
},
{
"path": "pi_sniffer/src/client.hpp",
"chars": 2021,
"preview": "#ifndef CLIENT_HPP\n#define CLIENT_HPP\n\n#include <string>\n#include <boost/thread.hpp>\n\n/**\n * This object represents an o"
},
{
"path": "pi_sniffer/src/configuration.cpp",
"chars": 5409,
"preview": "#include \"configuration.hpp\"\n\n#include \"util/convert.hpp\"\n\n#include <boost/filesystem/operations.hpp>\n#include <boost/fi"
},
{
"path": "pi_sniffer/src/configuration.hpp",
"chars": 2257,
"preview": "#ifndef CONFIG_HPP\n#define CONFIG_HPP\n\n#include <boost/unordered_map.hpp>\n#include <boost/cstdint.hpp>\n\n#include <string"
},
{
"path": "pi_sniffer/src/input/kismet_drone.cpp",
"chars": 6311,
"preview": "#include \"kismet_drone.hpp\"\n\n#include \"packet.hpp\"\n#include \"stats.hpp\"\n\n#include <iostream>\n#include <boost/lambda/bind"
},
{
"path": "pi_sniffer/src/input/kismet_drone.hpp",
"chars": 956,
"preview": "#ifndef KISMET_DRONE_HPP\n#define KISMET_DRONE_HPP\n\n#include <cstddef>\n#include <string>\n#include <boost/asio.hpp>\n\nclass"
},
{
"path": "pi_sniffer/src/input/pcap.cpp",
"chars": 6903,
"preview": "#include \"pcap.hpp\"\n#include \"packet.hpp\"\n\n#include <boost/lexical_cast.hpp>\n#include <boost/concept_check.hpp>\n#include"
},
{
"path": "pi_sniffer/src/input/pcap.hpp",
"chars": 485,
"preview": "#ifndef PCAP_HPP\n#define PCAP_HPP\n\n#include <fstream>\n#include <string>\n\n#include <boost/cstdint.hpp>\n\nclass Packet;\n\ncl"
},
{
"path": "pi_sniffer/src/main.cpp",
"chars": 12406,
"preview": "#include <cstdlib>\n#include <iostream>\n#include <boost/thread.hpp>\n#include <boost/program_options.hpp>\n\n#include \"util/"
},
{
"path": "pi_sniffer/src/packet.cpp",
"chars": 13327,
"preview": "#include \"packet.hpp\"\n\n#include \"client.hpp\"\n#include \"ap.hpp\"\n#include \"probed_network.hpp\"\n#include \"util/convert.hpp\""
},
{
"path": "pi_sniffer/src/packet.hpp",
"chars": 3721,
"preview": "#ifndef PACKET_HPP\n#define PACKET_HPP\n\n#include <string>\n#include <vector>\n#include <boost/thread.hpp>\n#include <boost/c"
},
{
"path": "pi_sniffer/src/probed_network.cpp",
"chars": 337,
"preview": "#include \"probed_network.hpp\"\n\nProbed_Network::Probed_Network() :\n m_accesslock(),\n m_name(),\n m_clients()\n{\n}\n"
},
{
"path": "pi_sniffer/src/probed_network.hpp",
"chars": 750,
"preview": "#ifndef PROBED_NETWORK_HPP\n#define PROBED_NETWORK_HPP\n\n#include <set>\n#include <string>\n#include <boost/cstdint.hpp>\n#in"
},
{
"path": "pi_sniffer/src/protocols/eapol11.cpp",
"chars": 1859,
"preview": "#include \"eapol11.hpp\"\n\n#include \"packet.hpp\"\n#include \"ap.hpp\"\n\n#include <netinet/in.h>\n#include <boost/static_assert.h"
},
{
"path": "pi_sniffer/src/protocols/eapol11.hpp",
"chars": 169,
"preview": "#ifndef EAPOL_HPP\n#define EAPOL_HPP\n\nclass Packet;\n\nclass EAPOL\n{\npublic:\n\n EAPOL();\n\n ~EAPOL();\n\n bool handle_"
},
{
"path": "pi_sniffer/src/protocols/ieee80211.cpp",
"chars": 21670,
"preview": "#include \"ieee80211.hpp\"\n\n#include \"packet.hpp\"\n#include \"client.hpp\"\n#include \"ap.hpp\"\n#include \"probed_network.hpp\"\n#i"
},
{
"path": "pi_sniffer/src/protocols/ieee80211.hpp",
"chars": 898,
"preview": "#include <cstddef>\n#include <boost/scoped_ptr.hpp>\n#include <boost/cstdint.hpp>\n\n#include \"llcsnap.hpp\"\n#include \"util/p"
},
{
"path": "pi_sniffer/src/protocols/llcsnap.cpp",
"chars": 562,
"preview": "#include \"llcsnap.hpp\"\n\n#include <sstream>\n#include <netinet/in.h>\n\n#include \"packet.hpp\"\n\nLLCSNAP::LLCSNAP() :\n m_ea"
},
{
"path": "pi_sniffer/src/protocols/llcsnap.hpp",
"chars": 214,
"preview": "#ifndef SNAP_HPP\n#define SNAP_HPP\n\nclass Packet;\n\n#include \"eapol11.hpp\"\n\nclass LLCSNAP\n{\npublic:\n LLCSNAP();\n\n ~L"
},
{
"path": "pi_sniffer/src/stats.cpp",
"chars": 1586,
"preview": "#include \"stats.hpp\"\n\nStats::Stats() :\n m_accesslock(),\n m_unecrypted(0),\n m_wep(0),\n m_wpa(0),\n m_wps(0)"
},
{
"path": "pi_sniffer/src/stats.hpp",
"chars": 1658,
"preview": "#ifndef STATS_HPP\n#define STATS_HPP\n\n#include <boost/thread.hpp>\n#include <boost/cstdint.hpp>\n\n/**\n * Stats is a an obje"
},
{
"path": "pi_sniffer/src/util/convert.cpp",
"chars": 5296,
"preview": "#include \"convert.hpp\"\n\n#include <string>\n#include <cassert>\n#include <stdexcept>\n#include <cctype>\n#include <algorithm>"
},
{
"path": "pi_sniffer/src/util/convert.hpp",
"chars": 398,
"preview": "#include <string>\n#include <cstddef>\n#include <boost/array.hpp>\n#include <boost/cstdint.hpp>\n#include <tins/hw_address.h"
},
{
"path": "pi_sniffer/src/util/kml_maker.cpp",
"chars": 5863,
"preview": "#include \"kml_maker.hpp\"\n\n#include <algorithm>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n#include <ioman"
},
{
"path": "pi_sniffer/src/util/kml_maker.hpp",
"chars": 824,
"preview": "#ifndef KML_MAKER_HPP\n#define KML_MAKER_HPP\n\n#include <map>\n#include <string>\n#include <vector>\n#include <boost/ptr_cont"
},
{
"path": "pi_sniffer/src/util/pcap_output.cpp",
"chars": 1855,
"preview": "#include \"pcap_output.hpp\"\n\n#include <boost/cstdint.hpp>\n\n#include \"../packet.hpp\"\n\nnamespace\n{\n #pragma pack(push, 1"
},
{
"path": "pi_sniffer/src/util/pcap_output.hpp",
"chars": 335,
"preview": "#ifndef PCAP_OUTPUT_HPP\n#define PCAP_OUTPUT_HPP\n\n#include <string>\n#include <fstream>\n\nclass Packet;\n\n/*! Create PPI pca"
},
{
"path": "ui/display_handler.py",
"chars": 26315,
"preview": "import socket\nimport time\nimport board\nimport busio\nimport re\nimport subprocess\nfrom enum import Enum\nimport adafruit_ss"
}
]
About this extraction
This page contains the full source code of the tenable/pi_sniffer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (152.5 KB), approximately 41.6k tokens, and a symbol index with 93 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.