Passive WiFi Tracking

In the last year or so, there have been quite a few stories on the use of passive WiFi tracking by advertisers, retailers, and analytics startups. Most of these articles focus on the significant privacy and security concerns associated with this practice, but few of them get into the details of how the technology works. Having built a similar system for my project, Casual Encounters, I think I can explain some of the inner workings of these systems, how to avoid being tracked, and how, for research purposes or to determine their own level of exposure, someone could build such a system. I will state that I am by no means an expert on wireless networks, signal analysis, or anything of the sort, but I have conducted a fair bit of research and trial and error, and it works for me. Your mileage may vary; don’t try this at home; etc, etc.

Probe Requests

When a WiFi client (phone, laptop, etc) is looking to connect to a known network, there are two approaches it can take. The first technique, which is used by laptops and most non-smartphone devices, involves scanning for Beacon Frames (packets broadcast by WiFi routers in order to advertise their presence), waiting for a network that the client has previously connected to, and initiating a connection with it. The second technique, which is used primarily by smartphones, involves periodically broadcasting packets called Probe Requests, which contain the unique MAC address of the client and (sometimes) the name of a network to which it has previously connected. The advantage of the second technique is that by actively scanning for nearby routers, a phone can initiate a wireless connection faster than if it waits for the router to send out a Beacon Frame. While this certainly makes it more convenient to hop on a network, it also makes it possible to indiscriminately gather up this data and use it to track people.

Monitor Mode

There are six modes in which a WiFi device can operate. Routers generally operate in Master mode, while clients operate in Managed mode. In order to pick up all nearby traffic, a device needs to be operating in Monitor mode. Once set to Monitor mode, the device no longer advertises its presence, so, barring any physical indicators, it can be difficult to determine if such a device is running nearby.


Avoiding being tracked by these kinds of systems is fairly simple, in theory. As long as you turn the WiFi radio off on your phone whenever you don’t explicitly need it (generally, when you’re away from home, work, or anywhere you have a trusted network), the phone will stop transmitting probe requests and you will be untrackable (at least through this technique). Obviously, from a practical perspective, manually turning WiFi off every time you leave your house could become quite annoying.

If you use an Android device, there are a number of apps available which can simplify the process. AVG PrivacyFix, for example, allows you to set a group of trusted networks, around which your WiFi radio will be enabled. Once you leave the vicinity of these networks, the app automatically disables your radio. There are a number of similar paid and free apps available for Android. I have not personally used any of them, so I can’t speak to their efficacy.

If you use an iOS device, your options are much more limited. Unless you have jailbroken your device, Apple’s sandboxing of third-party apps makes it impossible to build a utility to automatically disable WiFi. That being said, iOS 7 introduced a swipe up menu which provides quick access to a number of settings, including WiFi. It still requires manual intervention every time you leave your house, but it beats wading through the settings.

Building a WiFi Tracker

Building a device to track smartphones is reasonably straightforward. You can, in fact, just use your MacBook. I could probably end this tutorial there, with some quick instructions on installing Wireshark and filtering for probe requests, but that wouldn’t be very interesting, and it wouldn’t really answer the question of how to build a network of tracking devices to deploy around a city or retail environment, as mentioned in the articles at the top of this post.

If you were building a network of these trackers, it would be prohibitively expensive (and logistically difficult) to deploy a laptop for each tracking node. Since the computing needs of the tracker are quite low, you can get away with using something as simple and cheap as a Raspberry Pi with a wireless adapter or (my preferred option) a small travel router like the TP-LINK TR-3020, loaded with open firmware. Both approaches are inexpensive, offer a small form factor, and can be run off a 5V battery, making them ideal for this sort of project.

The setup process for a Pi is actually quite a bit easier than the MR-3020, since most of the filesystem setup is taken care of for you, but I prefer the router, as it offers a cheap, standalone solution. For that reason, I will be detailing the process for the router here, but if you choose to go with a Raspberry Pi, keep the following things in mind:

  • You can likely skip ahead to Setting up Monitor Mode, since the earlier steps are just meant to get the router’s filesystem and swap space set up.
  • The two devices run different versions of Linux, so certain configuration files will be in different locations and you will use a different package manager than opkg.
  • More powerful radios, such as the AWUS036H, may require a powered USB hub, since they draw more current than the Pi can supply through its USB ports.

Setting up your router

Before we proceed, you will need:

  • A TP-LINK MR-3020 router ($34.99 on Amazon. Similar routers, such as the TP-LINK TL-WR703N should work, although I haven’t tried them.
  • A USB flash drive (2-4 GB should be plenty, although more is always better)
  • An ethernet cable

The decision to use an MR-3020 came from my experience building a PirateBox (a wonderful project devised by David Darts, which has been adapted to run on a range of devices). The initial setup steps for this project and for a PirateBox are identical, and since David did such a fine job explaining them, I thought I would simply link to his instructions ((, rather than copy them all out again. You should follow these instructions up to (but not including) the section titled “Install PirateBox”.

Setting up your USB drive

If you followed the instructions in the previous section, you should now have SSH access to your router and the router should have access to the Internet. We will now configure our USB drive to extend the filesystem on the router and provide additional memory.

  1. Format your USB drive into two partitions: a primary Ext4 partition and a swap partition. The swap partition should be between 256 and 512MB.
  2. SSH into the router.
  3. Install packages to support the Ext4 filesystem:
    root@OpenWrt:~# opkg update
    root@OpenWrt:~# opkg install block-mount kmod-fs-ext4 
  4. Plug the USB drive into the router.

  5. Check that the drive and partitions are being detected:

    root@OpenWrt:~# ls /dev | grep sda

Setting up the filesystem

Now we will setup sda1 as a pivot overlay on the root file system, as described here:

root@OpenWrt:~# mkdir /mnt/sda1
root@OpenWrt:~# mount /dev/sda1 /mnt/sda1

Check that the drive mounted successfully (should return /dev/sda1 on /mnt/sda1 type ext4):

root@OpenWrt:~# mount | grep sda1

Copy files from the router’s flash storage to the usb drive. This will ensure that all of the necessary configuration files are available when we reboot with the USB drive replacing the root file system, so that the network interfaces come up as expected.

root@OpenWrt:~# tar -C /overlay -cvf - . | tar -C /mnt/sda1 -xf -

Edit /etc/config/fstab to automount /dev/sda1.

root@OpenWrt:~# vi /etc/config/fstab

Use the following configuration.

config global automount
    option from_fstab 1
    option anon_mount 1

config global autoswap
    option from_fstab 1
    option anon_swap 0

config mount
    option target   /overlay
    option device   /dev/sda1
    option fstype   ext4
    option options  rw,sync
    option enabled  1
    option enabled_fsck 0

config swap
    option device   /dev/sda2
    option enabled  0

Now reboot the router:

root@OpenWrt:~# reboot

Once all of the lights on the router have come back on, SSH into the router again and check that the USB drive mounted properly.

root@OpenWrt:~# mount | grep sda1
/dev/sda1 on /overlay type ext4 (rw,sync,relatime,user_xattr,barrier=1,data=ordered)

If you can’t SSH into the router, something might have gone wrong with copying the configuration files over to the USB drive. Unplug the USB drive from the router and restart the router by unplugging its power cable then plugging it back in. Leave the USB drive unplugged so it doesn’t mount. Once the router has restarted and you can SSH into it, plug the USB drive back in and go back through the previous steps to make sure you did them correctly.

Setting up the swap partition

The router does not have very much on-board memory, so if we try to execute any long-running processes, it will likely run out of memory and reboot itself. To check the available memory on the router, enter:

root@OpenWrt:~# free

You will notice that Swap has zeros across the board. We can use the swap partition we created earlier to ensure we have plenty of memory available. First, make sure the partition can function as swap:

root@OpenWrt:~# mkswap /dev/sda2

Then turn activate the swap space:

root@OpenWrt:~# swapon /dev/sda2

Now run free again to make sure the space was allocated:

root@OpenWrt:~# free
             total         used         free       shared      buffers
Mem:         29212        19160        10052            0         1972
-/+ buffers:              17188        12024
Swap:       475644            0       475644    

This is great, but it won’t stay active if we reboot the system, so we need to let the system know that it should activate swap every time it starts up. You may have noticed a swap section in our fstab file from earlier. In my experience, this doesn’t always activate properly, so I have chosen to ignore it and create a separate startup script to turn on the swap space. This has the added benefit of introducing us to startup scripts, in case we want to create one later to ensure our scanning script restarts when the system resets.

Swap Startup Script

We will start by creating the startup script:

root@OpenWrt:~# vi /etc/init.d/swapon

Enter the following into the file, then save it:

#!/bin/ash /etc/rc.common


start() {
    echo "start swap"
    swapon /dev/sda2

    echo "stop"

Make the script executable:

root@OpenWrt:~# chmod +x /etc/init.d/swapon

Now we need to make a symlink from /etc/rc.d to our script to make the system run it on startup:

root@OpenWrt:~# ln -s /etc/init.d/swapon /etc/rc.d/S109swapon

If you’re curious, the S109 part of the link name tells the system in which order the script should be run. If you list the files in /etc/rc.d, you will see that they all start with S##. S109 should put our swap script at the end of the list, so it will run after all of the system scripts.

Now reboot the system, SSH back in, and check if the swap space has been activated:

root@OpenWrt:~# free

             total         used         free       shared      buffers
Mem:         29212        19276         9936            0         2152
-/+ buffers:              17124        12088
Swap:       475644            0       475644

If the swap didn’t activate, double check that you set the swapon script to executable.

Set up Monitor Mode

Now that the system is (mostly) set up, we can get to the fun stuff. We will need to modify the router’s wireless config in order to activate it and set it to monitor mode:

root@OpenWrt:~# vi /etc/config/wireless

Comment out the line that disables wifi:

#option disabled 1

Use the following settings for wifi-iface:

config wifi-iface
    option device   radio0
    option network  lan
    option mode     monitor
    option hidden 1

Then restart the wifi interface:

root@OpenWrt:~# wifi down; wifi up

You may see some error messages, like the ones below, but the wireless should still activate properly.

ifconfig: SIOCSIFHWADDR: Invalid argument
command failed: Device or resource busy (-16)

Check that wireless is up and in monitor mode:

root@OpenWrt:~# iwconfig
lo        no wireless extensions.

wlan0     IEEE 802.11bgn  Mode:Monitor  Frequency:2.412 GHz  Tx-Power=15 dBm
          RTS thr:off   Fragment thr:off
          Power Management:on

eth0      no wireless extensions.

br-lan    no wireless extensions.

Install required packages

Now we will install all of the packages and libraries required by our scanning script:

root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg upgrade tar wget
root@OpenWrt:~# opkg install python tcpdump unzip
root@OpenWrt:~# wget
root@OpenWrt:~# tar -xvf scapy-latest.tar.gz
root@OpenWrt:~# cd scapy*
root@OpenWrt:~# python install
root@OpenWrt:~# cd ..; rm -rf scapy*

Test the scanning script

Edit The default git package on busybox seems to have trouble with https, but you can download the source code as a zip file instead.

We will need to clone the scanning script from git:

root@OpenWrt:~# mkdir /overlay/scripts; cd /overlay/scripts
root@OpenWrt:/overlay/scripts# wget --no-check-certificate -O
root@OpenWrt:/overlay/scripts# unzip
root@OpenWrt:/overlay/scripts# mv edkeeble-wifi-scan-e2a08627f05d wifi-scan    

Because we’re responsible humans, we aren’t actually going to indiscriminately grab everyone’s probe requests. We’ll set up a whitelist, so we only print out requests from our own phones. Open the script in vi and edit WHITELIST to include your phone’s MAC address:

root@OpenWrt:/overlay/scripts# cd wifi-scan
root@OpenWrt:/overlay/scripts/wifi-scan# vi

WHITELIST = [‘00:00:00:00:00:00’,] # Replace this with your phone’s MAC address

Now test the script:

root@OpenWrt:/overlay/scripts/wifi-scan# python wlan0

With the script running, get your phone out. While it will still send probe requests if it is connected to a network, it seems to send them more frequently if it isn’t already connected. Go to your settings and disconnect from your current network, but leave wifi turned on. You should start to see probe requests show up in the terminal. You may notice that some of the requests have an SSID while others do not. Probe requests without an SSID are considered broadcasts, designed to elicit responses from all access points in range.

Press CTRL-c to stop the scanning script. If it doesn’t stop right away, hold down CTRL-c until you get back to the terminal prompt.


There you go. You now have a portable router which can track nearby smartphones through WiFi packets. Of course, our current script doesn’t do very much and could be improved immensely. It could, for example, be modified to hop channels and pick up more data, start logging data, tracking devices between multiple areas, etc.

Thanks for reading. I hope this article was helpful and shed some light on the specifics of how these tracking systems work. If you would like to reach me, you can find me on twitter @edkeeble.

  • Javier Velazquez Traut

    Great post Edward!

    • Javier Velazquez Traut

      Just a couple of things you may have forgotten.
      -I couldn’t clone the repo from git directly since I got this error:
      “unable to find remote helper for https”
      After some research, it appears to be a problem from the dependencies in the git makefile. I couldn’t actually solve it, so I ended up downloading the repo directly from git as a zip.
      -You need to install tcpdump for the script to work correctly.

      • edkeeble

        You are absolutely correct on both counts. I wasn’t getting that git error before, but I’m seeing it now. I’ll try to find a solution, but in the meantime, I’ll direct people to the zip file. Thanks for the feedback!

  • Jens

    iOS uses randomized macs while scanning.

  • adamloving

    This could be perfect for my project (plays your theme song when you walk in the office). Rather than polling for new devices, this would be much more responsive, and support people that hadn’t joined our office wifi.

    • edkeeble

      That’s a fantastic project. One thing to keep in mind is the probe requests tend to be transmitted at somewhat random intervals, so you won’t necessarily detect a new device as soon as it comes within range.

  • Jeremy Galloway

    Reminds me of CreepyDol presented at BlackHat last year. “CreepyDOL (Creepy Distributed Object Locator) is the name of the sensor networking project…”:

  • Shivam Mishra

    is it necessary to be in monitor mode to send crafted probe request using scapy ?
    i have send a broadcast probe request using scapy, but unable to capture it. using wireshark (capturing on interface attached to an AP in same area/zone)

    • Max Synack

      Unless you are in monitor mode, you will only see the non-wireless components of the traffic, as if you were on a wired connection. To see probes and wireless specific traffic you need to be in monitor mode.

  • Sheridan Gray

    Is it possible to make HTTP requests while the wireless card is in Monitor Mode? I’ve written a script to listen for certain packets and need to send that data up to my server. However, I don’t want to be restricted to a hard ethernet connection, but rather, would prefer to be able to use a wireless network.

    • Jorge Rios

      Did you find out a solution for this?

      • Sheridan Gray

        There are two solutions that I have found and implemented. The first is to have two wireless chips – one for internet and one for packet sniffing. The second is to sniff for X minutes, stop sniffing and connect to the internet to send data, and then repeat.

        • Jorge Rios

          Thanks Sheridan.. You mean usa a USB splitter and have the storage on one end and a usb/wifi on the other? I thought that was not viable.. The second approach is pretty smart, just more fall backs to it I guess, need to switch configurations and somehow make sure they got in correctly, but I really like it… thanks for your input!

          • JuanJo Mastro

            Hi, I found another solution. You can create a virtual network interface. One of this in monitor mode, and other in sta mode.

  • Michael Stone

    I’ve been running your script on both the TL-MR3020 and a Raspberry Pi and there seems to be a memory leak. It only runs for about 15 minutes before the process consumes all available memory and the system grinds to a halt.

    Is anyone else noticing this behaviour?

  • jack_harvard

    Hey thanks for the post!! Great you got comments enabled. Any thoughts on how you will push the data to the server + get the device to automatically start logging on boot up?

  • kukat

    Thanks for the great post.

    But did you noticed that the CPU usage is 100%!

    • edkeeble

      Thanks kukat.

      I chose the TR-3020 because I find the form factor interesting and it’s cheap, but it’s definitely underpowered, as far as processing goes. The sniff process is running constantly, as well, so it isn’t too suprising that it overwhelms the processor (this is also what fills up available memory, as Michael Stone pointed out, since sniff seems like it doesn’t release any of its captured packets until it stops running).

      You could certainly modify the script to consume fewer resources and be more robust, but I’m not particularly interested in building this out as a fully-deployable system. I was more interested in demonstrating how this kind of system works and messing around with the 3020.

      Thanks again for the feedback!

  • Sheridan Gray

    Great article!

    Does adjusting txpower affect the radius of the MR-3020’s monitor mode or is that just used for transmitting wireless signals? I’m trying to limit the range in which the device picks up wifi devices.

    • edkeeble

      I’m not sure of a way to limit the range in which the router receives signals, short of adjusting the physical environment around it. Adjusting the txpower of the router will only limit the range in which it can transmit signals.

      • Sheridan Gray

        Ok – thanks. That was what I was expecting but thought I would confirm. Thanks again.


    Hey thanks for the post. how could i scan all the devices around me without any whitelist..

  • Lilmcnessy

    When I run this command:

    python install

    I get this:

    Could not find platform independent libraries
    Could not find platform dependent libraries
    Consider setting $PYTHONHOME to [:]
    ImportError: No module named site

  • Bubleek

    Thanks for the post!!! It’s look like fantastic, but on my router this script not working correctly!

    Can you tell me with what chipset and driver this script working?

    • Lilmcnessy

      It can be a bit slow at picking up devices, and some devices tend not to send many probe requests out. What is your device you have added to the whitelist?

      • Bubleek

        Yes, i add my smartphone Huawei honor 4x but it does not metter becouse Airodump-ng show me too many devices(connected and not with wifi point). Aerodump-ng working so fast(few seconds). I have more wifi devices around me, but your script don’t show me nothing. What chipset and driver you use in you wifi point?

    • Mert Yılmaz

      I’ve tried with both Iphone 5 and LG G4. However, the result is the same. Did you find any solution?

      • Bubleek

        No. This script not working correctly. I use another packege who called as Aircrack-ng and small utilite airodump-ng included in Aiecrack-ng packege. Try using it, may be it shold working more good and stable.

  • austin_h

    Hey, thanks for taking the time to document this. I followed your instructions up to the point where the USB is setup and it requests to install the required packages. (
    opkg install python tcpdump unzip)

    At this point it seems that I am unable to connect to the internet to get the packages…..although I can ping my gateway server ok. A ping to anything outside on the web returns bad request. Did I miss something? Many Thanks

    • Lilmcnessy

      Perhaps you have setup your ips incorrectly?
      What are your settings when you

      vi /etc/config/network

  • Lilmcnessy

    I’ve set it all up on a Raspberry Pi but for some reason the get the same RSSi of -256

  • Robert Maclean

    Hi. Sorry – am a newbie and not particularly technical. Would it be possible for a smartphone to be able to sniff out probe requests? I’m thinking for an app that detects when a new/ unknown device comes within range.

    • bendahrooge

      You would need higher level access to the phone’s operating system. Typically the operating systems of smart phones are totally locked down and there is no way to get root access to the terminal or execute unsigned code.

  • Mark Stanley

    This is a brilliant tutorial – thanks very much Ed, works a treat :-)

  • Harshit Lalpura

    Hello. I want to know that can I use this for Indoor Positioning ? And device monitoring is still possible if it is connected to router?

  • Marcelo Beraldi

    HI Edward. Great Post. The script is not reporting RSSI value (-256 always). I am using openwrt with 15.05 Chaos Calmer and TP-Link MR3020. Do you know any packet change format?

  • Andrew Crisp

    I cannot download the zip file from bitbucket, it just gives me a segmentation fault error, any suggestions?

    root@OpenWrt:/overlay/scripts# wget –no-check-certificate -O
    Connecting to (
    Segmentation fault

  • randall smith

    After upgrading to Openwrt my router can’t detect my usb! My usb is formatted to ext4 and I have the opkg install block-mount kmod-fs-ext4 package installed. (I have also tried Fat32). Also, I checked and confirmed that the usb port is not faulty.

    When I ls /dev no sda’s appear.

    Please help!