Tracking Wi-Fi Devices with Python and GPS

by Columbo

It's no secret that in today's society most people are tracked in one way or another.  This tracking is often morally ambiguous.  One could argue that bytracking your every movement Google is making life more convenient.  Google can tell you when there's a lot of traffic on your morning commute.  Google would love to give you directions to wherever you want to go.  Google knows where you work, and even where you live.  Some may argue that this goes a bit too far.

That being said, I'm not writing this for the sole purpose of starting a discussion about morals.  I'm writing this to introduce you to a Python script I made for this article called Wifitrack.  Wifitrack was built to run on Linux.  Wifitrack can track and map Wi-Fi devices.  I did not design this script with any specific use case in mind.  I created it for fun, and to possibly open some people's eyes to methohs of tracking that they would have been unfamiliar with otherwise.

You might question if this tracking script I've put together is immoral.  I do not believe that the program itself is immoral, but I do encourage you to only use it for good.  I think exploring this world of technology is essential for keeping things healthy.  Some things may need to change in the future, but we'll never know what to change if we don't experiment.

In the process of making this, my eyes were opened to things that made me feel like some big changes should be made either to the Wi-Fi spectrum itself or in device manufacturers' code.  The main thing that comes to mind is that a device's real MAC address (a unique identifier) is thrown all over the place in clear text even when it's connected to an encrypted network.  It would be easy to spoof the MAC address every time it connects, but almost no one does this.  This set of conditions allows Wifitrack to work.

Also, please note I will be using the term "hardware address" a lot instead of "MAC address."  An access point's MAC address is also known as a BSSID, so if I'm talking about either type, I will just say "hardware address."

This Python script that I wrote has several options.  The first two options are merely for prepping your hardware and the last option returns your Wi-Fi card to normal.  The real fun starts with Option #3, which uses a GPS device and a Wi-Fi card in Monitor Mode.  It can map out all the devices in a given area.  You could in theory make note of how many of a specific manufacturer's TVs are in a certain area and compare that to another area.  You could drive by hospitals and get a look at all the equipment (probably including active medical devices) they have connected to Wi-Fi.  You could even drive by places with voting machines and see if it looks like any voting machines are connected to Wi-Fi.

Output from Wifitrack (Option #3 and Option #5) is easy to import into satellite mapping programs like Google Earth.  Give your output file a CSV (Comma Separated Value) extension and select the latitude field and the longitude field when importing.

A list of partial hardware addresses and the corresponding manufacturer name can be used to help identify devices.  Option #7 of Wifitrack will download a manufacturers list taken from Wireshark and make a number of necessary modifications to it so we can use it with the script (make sure you're still connected to the Internet and not in Monitor Mode).  If you'd rather make your own manufacturers list, you can create a CSV file with a row for the known beginning of a hardware address followed by another row with the name you want to use for that manufacturer.  You don't need this list, but you will be prompted for it unless you have one with the name hwvenderlist in the directory where you run wifitrack.  You can just press Enter and leave this field empty if you'd rather.

Another important thing to note about Option #3 is that it runs an instance of Airodump-ng in the background.  All of the data will be dumped to a CSV file and the filename will be the date and time you ran Option #3.  This file or files, depending on how many times you run this option, will give us some basic information about whatever devices are around.  Option #6 will sift through all of the data Airodump-ng dumps and give us device probes from a specific MAC address that you specify.  It will also say which access point this MAC address was associated with if any.  This brings me to my next point.

Our devices are often very noisy.  If you have the Wi-Fi enabled on your phone, even if you're not at home, there's a chance your device will try to probe your home network.  Why is this a big deal?  Well, there's a large wardriving database out there known as wigle.net.  You can search wigle.net for an access point's ESSID or BSSID (your access point's MAC address).  If your device is asking for your home network and you have a unique access point name, someone could look that up and find out where you live.  (The same can be said if you take a screenshot of all of your available access points and share it online.  Don't do that unless you don't mind being geo-located.)  The probes your device makes can also just tell a story about the places you've visited and the Wi-Fi you've connected to.

If you're trying to find a target from only one location, you might want to try using a blacklist file.  A blacklist file will let you ignore all the addresses on the blacklist.  Run Option #3 in the location when your target and presumably your target's device are not in the location.  You can use the output from the time your target wasn't around as a blacklist file for when your target is known to be in the location.  This will work better in a location with very little activity.  You will be prompted for a blacklist file every time you run Option #3, but you can leave the field blank if you don't want to use one.

The blacklist file could also be used to continue your progress on Option #3 if you have to restart wifitrack for any reason.  Just use the name of the output file you want to continue using as the blacklist file, and then again as the new output file name.  Wifitrack will just append the new data and you won't get any repeats of devices you already detected.

Option #4 of wifitrack may give you yet another reason to turn your Wi-Fi off in public places.  To clarify, I don't just mean you shouldn't connect to open Wi-Fi.  I mean if you don't want to be tracked, you should probably just disable Wi-Fi completely in public (not to mention Bluetooth or just your phone in general).  You could mitigate this to some degree by spoofing your MAC address every time you connect to an access point, but depending on which device you use, that could be more or less difficult.

Here's a bit of a thought experiment.  Let's say you wanted to find out the MAC address of Donald Trump's unsecured Android phone to force him to look at lolcatz.  Let's pretend that he carries this phone around the country to all sorts of public speaking events.  Let's also say that, hypothetically, the people surrounding him at these events change from day-to-day.  If you want to follow the president around, you can run Option #3 of Wifitrack every time you are in close proximity of the president.  Option #4 will let you compare multiple Option #3 outputs and find any hardware address matches from file-to-file.  If Trump keeps his Wi-Fi on, you could find his phone's MAC address by process of elimination.  Now that you have his phone's MAC address, you could work some wizardry to make him look at cute cats... but perhaps that's a topic for another article.

The last feature we have to talk about is Option #5.  This option requires that you enter at least one hardware address in a text file that you want to watch for.  You can add multiple addresses and you can label them by adding a comma immediately after the address followed by your label (Example: FF:FF:FF:FF:FF:FF,label).

You will also be given the option to run a command when you successfully come in range of the device(s).  I suggest something like: sudo -u username mpv beep.wav & to play a beep sound from whichever user you'd like.  Some audio players don't play well with root, which is what this program was designed for.  So in that example, you can select whichever non-root user you want to run a sound effect to alert you.

To test Option #5, I made a file that contained the hardware address of my smart TV and turned the TV on.  I then drove about a mile away, turned Option #5 on, and drove past my place of residence at 35 mph.  With no fancy antennas and only using the network card in the cheapest Netbook I could find, Wifitrack successfully picked up some packets and took note of the longitude and latitude.  Looking at the GPS coordinates on Google Earth, I noticed by coincidence or not that the plot point was directly across from the location of the TV.

I also discovered in testing that Option #5 could be an excellent alarm to alert you to someone using a specific device.  If you ban your child from the TV, you could use the TV's address and set a loud sound to play when it turns on.

In closing, maybe we should all be more careful about what we connect to the Internet.  All of these devices can be spied on.  Does it matter if a passerby can tell if I'm toasting bread with my smart toaster?  I'm not sure, but it sure feels wrong hooking a toaster up to the Internet.

Dependencies

When I tested this on the latest full live Kali build, I only needed to install gpsd and a Python module known as gps.  When I tested this on a live Ubuntu build, it was much more complicated.

If you're using Ubuntu (live or not), you might need to edit a gpsd configuration file located at: /etc/default/gpsd

If you're using a live version of Ubuntu, you will likely also need to edit or create the: /etc/apt/sources.list file to specify the correct repositories.  I would recommend you just use Kali.

The following commands should pull in any packages you could be missing (requires an Internet connection):

# apt update
# apt install aircrack-ng python python-scapy iwpython-pip tcpdump gpsd wget sed
# pip install gps

Important Links

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

#For best results, run as root in a safe enviornment.
#This is experimental software.  Use at your own risk.
#This requires the scapy python library, the aircrack-ng suite, gpsd, and the gps python library.
#Read the original article for further documentation.

from scapy.all import *
import os, sys, time, datetime
from gps import *
#Insert some threading to run multiple functions at the same time.
from threading import Thread
#Some variables to use globally later.
menu = ""
interface = ""
hwaddress = ""
#Optional vender list to view the names of the hardware venders.
venderlist = []
#Our output file's name.
file_name = ""
#Lattitude/Longitude global variables.
lat = 0.0
lon = 0.0
#Menu display.  Title font uses a figlet font named Bloody.  Requires utf coding.
def displaymenu():
    global menu 
    menu = raw_input("\n\
 █     █░ ██▓  █████▒██▓▄▄▄█████▓ ██▀███   ▄▄▄       ▄████▄   ██ ▄█▀\n\
▓█░ █ ░█░▓██▒▓██   ▒▓██▒▓  ██▒ ▓▒▓██ ▒ ██▒▒████▄    ▒██▀ ▀█   ██▄█▒ \n\
▒█░ █ ░█ ▒██▒▒████ ░▒██▒▒ ▓██░ ▒░▓██ ░▄█ ▒▒██  ▀█▄  ▒▓█    ▄ ▓███▄░ \n\
░█░ █ ░█ ░██░░▓█▒  ░░██░░ ▓██▓ ░ ▒██▀▀█▄  ░██▄▄▄▄██ ▒▓▓▄ ▄██▒▓██ █▄ \n\
░░██▒██▓ ░██░░▒█░   ░██░  ▒██▒ ░ ░██▓ ▒██▒ ▓█   ▓██▒▒ ▓███▀ ░▒██▒ █▄\n\
░ ▓░▒ ▒  ░▓   ▒ ░   ░▓    ▒ ░░   ░ ▒▓ ░▒▓░ ▒▒   ▓▒█░░ ░▒ ▒  ░▒ ▒▒ ▓▒\n\
  ▒ ░ ░   ▒ ░ ░      ▒ ░    ░      ░▒ ░ ▒░  ▒   ▒▒ ░  ░  ▒   ░ ░▒ ▒░\n\
  ░   ░   ▒ ░ ░ ░    ▒ ░  ░        ░░   ░   ░   ▒   ░        ░ ░░ ░ \n\
    ░     ░          ░              ░           ░  ░░ ░      ░  ░   \n\
\n\
To continue, type a number and then press enter:\n\
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\
Choose an option:\n\
1. Change into monitor mode with airmon-ng.\n\
2. Start gpsd and specify your gps device.\n\
3. Scan for all hardware addresses and write to file. ctrl-z to exit.\n\
4. Match hardware addresses from different file outputs.\n\
5. Scan for one or more specific hardware addresses from a file.\n\
6. Find probes and associated devices from a hw address.  This scans through your airodump-ng database.\n\
7. Create or update hardware vender file to identify most devices scanned.\n\
8. Stop monitor mode and return wifi to normal. \n\
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\
")


#---------------------definitions-----------------------    
def AddressScan(pkt) :
    global file_name
    global lat
    global lon
    splitstring = []
    f = open(file_name, "a")
    venderfound  = 0
    thetimeis = datetime.datetime.now()
    #This section looks for valid harddware addresses.  The length will be 17.  Then it looks through your hardware vender file
    #to figure out which type of device the address belongs to.  It also takes note of the date/time and gps coordinates.
    if pkt.addr1 not in clients and len(str(pkt.addr1)) == 17:
        clients.append(pkt.addr1)
        for line in venderlist:
            if len(line) > 2 and line[2] == ":":
                splitstring = line.split(',')
                if str(pkt.addr1)[:len(splitstring[0])] == splitstring[0].lower() and venderfound == 0:
                    f.write(str(pkt.addr1) + "," + splitstring[1].rstrip() + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
                    print "Device Found: %s - %s,%s,%s,%s" % ((pkt.addr1), splitstring[1].rstrip(), str(lat), str(lon),str(thetimeis))
                    venderfound = 1
        if venderfound == 1:
            venderfound = 0
        else:
            f.write(str(pkt.addr1) + ",unknown," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
            print "Device Found: %s,unknown,%s,%s,%s" % ((pkt.addr1), str(lat), str(lon), str(thetimeis))

    if pkt.addr2 not in clients and len(str(pkt.addr2)) == 17:
        clients.append(pkt.addr2)
        for line in venderlist:
            if len(line) > 2 and line[2] == ":":
                splitstring = line.split(',')
                if str(pkt.addr2)[:len(splitstring[0])] == splitstring[0].lower() and venderfound == 0:
                    f.write(str(pkt.addr2) + "," + splitstring[1].rstrip() + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
                    print "Device Found: %s - %s,%s,%s,%s" % ((pkt.addr2), splitstring[1].rstrip(), str(lat), str(lon), str(thetimeis))
                    venderfound = 1
        if venderfound == 1:
            venderfound = 0
        else:
            f.write(str(pkt.addr2) + ",unknown," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
            print "Device Found: %s,unknown,%s,%s,%s" % ((pkt.addr2), str(lat), str(lon), str(thetimeis))

    if pkt.addr3 not in clients and len(str(pkt.addr3)) == 17:
        clients.append(pkt.addr3)
        for line in venderlist:
            if len(line) > 2 and line[2] == ":":
                splitstring = line.split(',')
                if str(pkt.addr3)[:len(splitstring[0])] == splitstring[0].lower() and venderfound == 0:
                    f.write(str(pkt.addr3) + "," + splitstring[1].rstrip() + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
                    print "Device Found: %s - %s,%s,%s,%s" % ((pkt.addr3), splitstring[1].rstrip(), str(lat), str(lon), str(thetimeis))
                    venderfound = 1
        if venderfound == 1:
            venderfound = 0
        else:
            f.write(str(pkt.addr3) + ",unknown," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
            print "Device Found: %s,unknown,%s,%s,%s" % ((pkt.addr3), str(lat), str(lon), str(thetimeis))

def scancommand(pkt) :
    global file_name
    global hwaddressfile
    global lat
    global lon
    global systemcommand
    f = open(file_name, "a")
    if pkt.addr1 in clients:
        thetimeis = datetime.datetime.now()
        print "Device Detected: %s, %s, %s, %s, %s" % ((pkt.addr1), clients[pkt.addr1], str(lat), str(lon), str(thetimeis))
        f.write(str(pkt.addr1) + "," + clients[pkt.addr1] + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
        if systemcommand != "":
            os.system(systemcommand)
    if pkt.addr2 in clients:
        thetimeis = datetime.datetime.now()
        print "Device Detected: %s, %s, %s, %s, %s" % ((pkt.addr2), clients[pkt.addr2], str(lat), str(lon), str(thetimeis))    
        f.write(str(pkt.addr2) + "," + clients[pkt.addr2] + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
        if systemcommand != "":
            os.system(systemcommand)

    if pkt.addr3 in clients:
        thetimeis = datetime.datetime.now()
        print "Device Detected: %s, %s, %s, %s, %s" % ((pkt.addr3), clients[pkt.addr3], str(lat), str(lon), str(thetimeis))
        f.write(str(pkt.addr3) + "," + clients[pkt.addr3] + "," + str(lat) + "," + str(lon) + "," + str(thetimeis) + "\n")
        if systemcommand != "":
            os.system(systemcommand)
    f.close()

def channelhop():
    channel = 1
    while channel < 14:
        os.system("iw dev %s set channel %d" % (interface, channel))
        time.sleep(.01)
        channel = channel + 1
            
        if channel == 13:
            channel = 1

#This feature requires you to set up gpsd on your system.  Also requires the python module gps.
def gpsfunct():
    global lat
    global lon
    gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE)
    while True:
        report = gpsd.next()
        if report['class'] == 'TPV':
            lat = getattr(report,'lat',0.0)
            lon = getattr(report,'lon',0.0)

def airodumpdatabase():
        #Run airodump and save all the data. we can refer to this data later.  This line uses the -K 1 option to run airodump-ng in the
        #background.  If this option isn't used airodump-ng seems to override the output.  This will keep on running even after the
        #python script is closed.  You may want to close it manually when you're finished.
        os.system('airodump-ng -K 1 -w' + "aird-db/" + str(datetime.datetime.now()).replace(" ","") + ' --output-format csv ' + interface)
#---------------------menu------------------------------ 
while True:
    displaymenu()
    if menu == "1":
        os.system("clear")
        #Assumes the user has iwconfig.  Shows available interfaces.
        os.system("iwconfig")
        #User inputs preferred wireless interface
        interface = raw_input("Please enter your wireless interface: (ex. wlan0)\n")
        #Device is turned off and then put into monitor mode
        os.system("ip link set dev " + interface + " down")
        os.system("airmon-ng start " + interface)
        #If you type in your interface name incorrectly you should restart.  The other options will assume you succesfully entered monitor mode.
        interface = interface + "mon"
    if menu == "2":
        gpsdevice = raw_input("Please enter your gps device. (ex. /dev/ttyUSB0)\n")
        os.system("gpsd " + gpsdevice + " -F /var/run/gpsd.sock")
    if menu == "3":
        #Start GPS function so that can load while prompts are entered.
        Thread(target = gpsfunct).start()
        clients = []
        clients.append("ff:ff:ff:ff:ff:ff")
        #User inputs interface if string is empty.
        if interface == "":
            os.system("clear")
            os.system("iwconfig")
            interface = raw_input("Please enter your wireless interface: (ex. wlan0mon)\n")
        if os.path.exists("hwvenderlist"):
            venderfile = "hwvenderlist"
        else:
            venderfile = raw_input("Please enter the name of the file with hardware venders, or leave this blank.\n")
        if venderfile != "":
            vf = open(venderfile,"r")
            for line in vf:
                venderlist.append(line)
            vf.close()
        blacklistfile = raw_input("Enter the name of your blacklist file or leave this blank and press enter.\n")
        if blacklistfile != "":
            bl = open(blacklistfile,"r")
            for line in bl:
                #Truncate the line to 17 characters.
                clients.append(line[:17])
            bl.close()
        file_name = raw_input("Please name the output file.\n")
        if file_name == "":
            file_name = "wt-option3-default-output-" + str(datetime.datetime.now())
        #Checks for airodump-database directory.  Creates it if it doesn't exist.  We can use these files later.
        if os.path.exists("aird-db") == False:
            os.system("mkdir aird-db")
        
        #Press ctrl c OR ctrl Z to stop scripts
        #Runs our channel hopper, address scanner, and airodump-ng database.
        Thread(target = airodumpdatabase).start()
        Thread(target = channelhop).start()
        Thread(target = sniff(iface=interface, prn = AddressScan)).start()
    if menu == "4":
        list1 = []
        list2 = []
        file1 = raw_input("Enter the name of your first output file.\n")
        file2 = raw_input("Enter the name of your second output file.\n")
        savedfile = raw_input("If you would like to save the matches to a file enter a file name.\n")
        f1 = open(file1,"r")
        f2 = open(file2,"r")
        #Check to see if the user wants to save a file.  Otherwise you'll get an error.
        if savedfile != "":
            nf = open(savedfile,"a")
        for line in f1:
            list1.append(line.lower()[:17])
        f1.close()
        for line in f2:
            list2.append(line.lower()[:17])
        f2.close()
        for line in set(list1).intersection(list2):
            print line
            if savedfile != "":
                nf.write(line)
        raw_input("Press enter to return to menu.")
        os.system("clear")
    if menu == "5":
        Thread(target = gpsfunct).start()
        if interface == "":
            os.system("clear")
            os.system("iwconfig")
            interface = raw_input("Please enter your wireless interface: (ex. wlan0mon)\n")
        clients = {}
        hwaddressfile = raw_input("Please enter the filename that contains the addresses you would like to scan for\n")
        file_name = raw_input("Enter the name of the file to output successful scan info.  (date/time GPS)\n")
        if file_name == "":
            file_name = "wt-option5-default-output-" + str(datetime.datetime.now())
        systemcommand = raw_input("Enter a shell command to run on a successful scan. (ex. vlc ring.wav)\n")
        hwf = open(hwaddressfile,"r")
        splitstring = []
        for line in hwf:
            splitstring = line.split(",")
            if len(splitstring) > 1:
                clients.update({splitstring[0][:17].lower() : splitstring[1].rstrip()})
            else:
                clients.update({splitstring[0][:17].lower() : "no name"})
        Thread(target = channelhop).start()
        Thread(target = sniff(iface=interface, prn = scancommand)).start()
        hwf.close()
    if menu == "6":
        splitstring = []
        airdfile = []
        airddb = os.listdir("aird-db")
        clientmac = raw_input("Please enter the mac address of the client.\n")
        clientmac = clientmac.upper()
        for line in airddb:
            ad = open("aird-db/" + line,"r")
            #We start scanning from the bottom.  The first line we need is len(airdfile)-2.
            linenum = 2
            for line in ad:
                airdfile.append(line)
            #Checks for a colon on the 3rd character of the line.  If it's there it should be a client.
            while airdfile[len(airdfile)-linenum][2] == ":":
                splitstring = airdfile[len(airdfile)-linenum].split(',')
                #Prints out the associated client.
                if splitstring[0] == clientmac:
                    print "Associated AP:"
                    print splitstring[5]
                if splitstring[0] == clientmac and len(splitstring[6]) != 2:
                    for probe in range(len(splitstring) - 6):
                        print "Probe:"
                        print splitstring[6 + probe]
                linenum = linenum + 1
            ad.close()
        raw_input("Press enter to return to menu.")
        os.system("clear")
    
    if menu == "7":
        #We start out with the wireshark manuf file.  That has all the info we need.  It just has to be modified.
        os.system("wget -O hwvenderlist-tempfile-delete https://raw.githubusercontent.com/wireshark/wireshark/master/manuf")
        #Get rid of all the commas.  We need to turn this into a csv file of sorts.
        os.system("sed -i 's/,//g' hwvenderlist-tempfile-delete")
        #Replace the first tab on each line with a comma.  This should separate all the hardware addresses.
        os.system("sed -i 's/\\t/,/' hwvenderlist-tempfile-delete")
        #Truncate the "netmasks" after the specified number of bits.
        os.system("sed -i 's/0:00\\/36//' hwvenderlist-tempfile-delete")
        os.system("sed -i 's/0:00:00\\/28//' hwvenderlist-tempfile-delete")
        splitstring = []
        ieeereg = ""
        #We need to move all the IeeeRegi addresses to the bottom.  Some are redundant after modifying the netmasks.
        with open("hwvenderlist-tempfile-delete", "r") as fdownload:
            with open("hwvenderlist", "w") as output:
                output.write("# This file has been modified for use with wifitrack.  Sorry for any confusion.\n")
                for line in fdownload:
                    splitstring = line.split(",")
                    if len(splitstring) > 1 and splitstring[1][:8] == "IeeeRegi":
                        ieeereg = ieeereg + line
                    else:
                        output.write(line)
                output.write(ieeereg)
        fdownload.close()
        output.close()
        os.system("rm hwvenderlist-tempfile-delete")
        
    if menu == "8":
        #User inputs preferred wireless interface.
        if interface == "":
            os.system("iwconfig")
            interface = raw_input("Please enter your wireless interface: (ex. wlan0mon)\n")
        #Device is turned off and then put into monitor mode.
        os.system("airmon-ng stop " + interface)

Code: wifitrack.py Python 2

Code: wifitrack.py Python 3

Columbo2600 Wifitrack Readme Date: September 11th 2020

Note: A few things have changed since I wrote the article (I finished it a year and one day ago). 

Python 2 was officially deprecated in January so the dependencies are slightly different as you have to 
install scapy through pip. 

I may go ahead and convert the program to Python 3 in the near future. 

For now I've included the instructions to get it to work in the new Kali Linux.

Installation Instructions for Kali 2020.3 Live Media:

    Connect to the internet.
    Obtain wifitrack.py from the Columbo2600 github.
    Open the terminal and log in to root. type "sudo su"
    Install some dependencies with the following command "apt install python-pip gpsd"
    Install the rest of the dependencies with the dependencies with the following command "pip install gps scapy"
    Navigate to the folder with wifitrack. (ex. "cd /home/kali/Downloads")
    Run wifitrack "python2 wifitrack.py"

(Update: Python 3 version now available. 

If you choose that version make sure you install python3-pip instead of python-pip. 

When installing gps and scapy modules use the command "pip3 install gps scapy".)
Return to $2600 Index