├── Pipfile ├── Pipfile.lock ├── license.md ├── readme.md ├── requirements.txt └── wifi-users.py /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | 5 | [packages] 6 | netifaces = "*" 7 | netaddr = "*" 8 | wireless = "*" 9 | tqdm = "*" 10 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "tqdm": { 4 | "version": "==4.11.2", 5 | "hash": "sha256:1621b3476c2224045d8bd57209aab006612a46ae2cc518b4a92ad988983e4c3d" 6 | }, 7 | "wireless": { 8 | "version": "==0.3.2", 9 | "hash": "sha256:e28d48a3e7e00be100be8a14c27be4f8cbde30dddecd9f6f3ffc3c03184967de" 10 | }, 11 | "netaddr": { 12 | "version": "==0.7.19", 13 | "hash": "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca" 14 | }, 15 | "netifaces": { 16 | "version": "==0.10.5", 17 | "hash": "sha256:59d8ad52dd3116fcb6635e175751b250dc783fb011adba539558bd764e5d628b" 18 | } 19 | }, 20 | "develop": {}, 21 | "_meta": { 22 | "sources": [ 23 | { 24 | "url": "https://pypi.python.org/simple", 25 | "verify_ssl": true 26 | } 27 | ], 28 | "requires": {}, 29 | "hash": { 30 | "sha256": "4bc9bce1339bcb0762bd8cbed54b84932fb7d9b3ace28db56a8fd8ae6b6a4491" 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The code in this repository is available under the [MIT License](https://secure.wikimedia.org/wikipedia/en/wiki/Mit_license). 2 | 3 | Copyright (c) 2017- Kyle McDonald 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Free Wifi 2 | 3 | This short tutorial describes a few methods for gaining access to the Internet, [a basic human right](https://en.wikipedia.org/wiki/Right_to_Internet_access#2011:_UN_Special_Rapporteur_report), from public wireless networks. 4 | 5 | This tutorial has been tested on Mac and a Raspberry Pi. It should generally work on Linux, and hasn't been tested on Windows. 6 | 7 | ## Preparation 8 | 9 | Make sure you do this step *before* you are stuck without Internet access: 10 | 11 | 1. Install [Python pip](https://pip.pypa.io/en/stable/installing/) 12 | 2. On Linux, install Python Developer package, a dependency for the `netifaces` package. 13 | 14 | **Ubuntu:** 15 | ``` 16 | $ sudo apt-get install python-dev 17 | ``` 18 | 19 | **Fedora:** 20 | ``` 21 | $ sudo dnf install python-devel 22 | ``` 23 | Note: For Centos, substitute `dnf` with `yum` 24 | 25 | 2. Make a copy of this repository and install dependencies for the script: 26 | 27 | ``` 28 | $ git clone https://github.com/kylemcdonald/FreeWifi 29 | $ cd FreeWifi && sudo pip install -r requirements.txt 30 | ``` 31 | 32 | ## How to get additional time 33 | 34 | If you had free internet access but your time has run out, the first thing to try is open an incognito/private window. Here are instructions for a few browsers: 35 | 36 | * [Chrome](https://support.google.com/chrome/answer/95464?source=gsearch&hl=en) (mobile and desktop) 37 | * [Safari for iOS](https://support.apple.com/en-us/HT203036) 38 | * [Safari for Mac](https://support.apple.com/kb/ph21413?locale=en_US) 39 | * [Microsoft Edge](https://support.microsoft.com/en-us/instantanswers/34b9a3a6-68bc-510b-2a9e-833107495ee5/browse-inprivate-in-microsoft-edge) 40 | 41 | An incognito/private window will temporarily clear any cookies that may have been used for tracking how much time you spent online, making you look like a "new user" and allowing you to log into the wireless portal again. 42 | 43 | Unfortunately, most systems track MAC addresses instead of cookies. A MAC address is a unique identifier assigned to every network interface. This means you need to get a new MAC address to get additional time. Fortunately, MAC addresses can be changed in software, without swapping the hardware. The `spoof-mac` command line utility makes this easy by entering `sudo spoof-mac randomize Wi-Fi`. If the command fails to run, try entering `spoof-mac list --wifi` to check what the name of your wireless device is first, and use that manually. After randomizing your MAC, try logging into the wireless portal again. When you're done using the Internet, run `sudo spoof-mac reset Wi-Fi` to reset your MAC address. 44 | 45 | Note that MAC address spoofing may be interpreted as an illegal activity depending on why you do it. In some cases it is certainly not illegal: recent mobile operating systems like iOS 8+ and Android 6+ automatically randomize their MAC address when searching for wireless networks to avoid being tracked. But when [Aaron Swartz liberated JSTOR](https://en.wikipedia.org/wiki/MAC_spoofing#Controversy), MAC address spoofing was claimed as a signal of intention to commit a crime. 46 | 47 | ## How to get free access 48 | 49 | If the network is open, but you can't get access for some reason, you can also try spoofing the MAC address of a device that is already using the network. To the router, your device and the other device will look like one device. This can cause some minor problems if they interrupt each other, but for light browsing it usually works out fine. 50 | 51 | To find the MAC addresses of other devices using the network, first you need to connect to the network. You don't need to have Internet access, just a connection. First, on Mac OS run the command `sudo chmod o+r /dev/bpf*` once to make sure you can sniff wireless data (you need to do this again if you restart your computer). Then run the command `python wifi-users.py`. You should see a progress bar immediately: 52 | 53 | ``` 54 | Available interfaces: en0 55 | Interface: en0 56 | SSID: nonoinflight 57 | Available gateways: en0 58 | Gateway IP: 10.0.1.1 59 | Gateway MAC: 00:e0:4b:22:96:d9 60 | 100%|██████████████████████████| 1000/1000 [00:46<00:00, 21.46it/s] 61 | Total of 5 user(s): 62 | 27:35:96:a8:66:7f 6359 bytes 63 | 36:fe:83:9c:35:eb 9605 bytes 64 | 65:01:3c:cc:20:e8 17306 bytes 65 | 8c:6f:11:2c:f0:ee 20515 bytes 66 | 0a:4f:b2:b8:e8:56 71541 bytes 67 | ``` 68 | 69 | If there isn't much traffic on the network, it might take longer. If it's taking too long, type `CTRL-C` to cancel the sniffing and print whatever results are available. Finally, we want to spoof one of these MAC addresses. For example, in this case we would enter `sudo spoof-mac set 0a:4f:b2:b8:e8:56 Wi-Fi` to try spoofing the address with the most traffic (they probably have a connection). After running that command, try to access the Internet. If you don't have a connection, try the next MAC in the list. If your Internet connection drops out while using this MAC address, try disconnecting and reconnecting to the wireless network. Note that the original user of the MAC you copied may experience these same connection drop outs if you are both actively using the network. 70 | 71 | ### How it works 72 | 73 | `wifi-users.py` uses `tcpdump` to collect wireless packets. Then we look through these packets for any hints of the MAC address (BSSID) of our wireless network. Finally, we look for data packets that mention a user's MAC as well as the network BSSID (or the network gateway), and take note of that MAC using some amount of data. Then we sort the user's MACs by the total amount of data and print them out. 74 | 75 | Instead of sniffing wireless traffic, in some situations you can also use the command `arp -a` to get a list of MAC addresses of devices on the wireless network. Then you can either use `spoof-mac` to copy the address, or use `ifconfig` directly on Linux and OSX. For the specifics of using `ifconfig` look at the implementations of `set_interface_mac` inside [SpoofMac's interfaces.py](https://github.com/feross/SpoofMAC/blob/master/spoofmac/interface.py). 76 | 77 | *This repository is dedicated to Lauren McCarthy, who has taught me the most about the art of getting a good deal.* 78 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | netifaces 2 | netaddr 3 | wireless 4 | SpoofMAC 5 | tqdm 6 | -------------------------------------------------------------------------------- /wifi-users.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import subprocess 3 | import re 4 | import sys 5 | import argparse 6 | import os 7 | from collections import defaultdict 8 | 9 | import netifaces 10 | from netaddr import EUI, mac_unix_expanded 11 | from wireless import Wireless 12 | from tqdm import tqdm 13 | 14 | NO_SSID = 'No SSID is currently available. Connect to the network first.' 15 | NO_WIRELESS = 'Error getting wireless interface.' 16 | NO_GATEWAY_MAC = 'Error getting gateway MAC address.' 17 | 18 | def eprint(*args, **kwargs): 19 | print(*args, file=sys.stderr, **kwargs) 20 | 21 | 22 | def run_process(cmd, err=False): 23 | err_pipe = subprocess.STDOUT if err else open(os.devnull, 'w') 24 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=err_pipe) 25 | while True: 26 | retcode = p.poll() 27 | line = p.stdout.readline() 28 | yield line 29 | if retcode is not None: 30 | break 31 | 32 | 33 | def main(args): 34 | parser = argparse.ArgumentParser( 35 | description='Find active users on the current wireless network.') 36 | parser.add_argument('-p', '--packets', 37 | default=1000, 38 | type=int, 39 | help='How many packets to capture.') 40 | parser.add_argument('-i', '--interface', 41 | default=None, 42 | type=str, 43 | help='Which wireless interface to use.') 44 | parser.add_argument('-s', '--ssid', 45 | default=None, 46 | type=str, 47 | help='Which SSID to use.') 48 | parser.add_argument('-r', '--results', 49 | default=None, 50 | type=int, 51 | help='How many results to show.') 52 | args = parser.parse_args() 53 | 54 | try: 55 | if args.interface: 56 | iface = args.interface 57 | else: 58 | wireless = Wireless() 59 | ifaces = wireless.interfaces() 60 | eprint('Available interfaces: {}'.format(', '.join(ifaces))) 61 | iface = ifaces[-1] 62 | eprint('Interface: {}'.format(iface)) 63 | 64 | if args.ssid: 65 | ssid = args.ssid 66 | else: 67 | wireless = Wireless() 68 | ssid = wireless.current() 69 | if ssid is None: 70 | eprint(NO_SSID) 71 | return 72 | eprint('SSID: {}'.format(ssid)) 73 | except: 74 | eprint(NO_WIRELESS) 75 | raise 76 | 77 | mac_re_str = '([\dA-F]{2}:){5}[\dA-F]{2}' 78 | mac_re = re.compile(mac_re_str, re.I) 79 | network_macs = set() 80 | try: 81 | gws = netifaces.gateways()[netifaces.AF_INET] 82 | gw_ifaces = ', '.join([gw[1] for gw in gws]) 83 | eprint('Available gateways: {}'.format(gw_ifaces)) 84 | gw_ip = next(gw[0] for gw in gws if gw[1] == iface) 85 | eprint('Gateway IP: {}'.format(gw_ip)) 86 | gw_arp = subprocess.check_output(['arp', '-n', str(gw_ip)]) 87 | gw_arp = gw_arp.decode('utf-8') 88 | gw_mac = EUI(mac_re.search(gw_arp).group(0)) 89 | gw_mac.dialect = mac_unix_expanded 90 | network_macs.add(gw_mac) 91 | eprint('Gateway MAC: {}'.format(gw_mac)) 92 | except StopIteration: 93 | eprint('No gateway for {}'.format(iface)) 94 | except KeyError: 95 | eprint('No gateways available: {}'.format(netifaces.gateways())) 96 | except: 97 | eprint(NO_GATEWAY_MAC) 98 | 99 | bssid_re = re.compile(' BSSID:(\S+) ') 100 | 101 | tcpdump_mac_re = re.compile('(SA|DA|BSSID):(' + mac_re_str + ')', re.I) 102 | length_re = re.compile(' length (\d+)') 103 | client_macs = set() 104 | data_totals = defaultdict(int) 105 | 106 | cmd = 'tcpdump -i {} -Ile -c {} -s 0'.format(iface, args.packets).split() 107 | try: 108 | bar_format = '{n_fmt}/{total_fmt} {bar} {remaining}' 109 | progress = tqdm(run_process(cmd), 110 | total=args.packets, 111 | bar_format=bar_format) 112 | for line in progress: 113 | line = line.decode('utf-8') 114 | 115 | # find BSSID for SSID 116 | if ssid in line: 117 | bssid_matches = bssid_re.search(line) 118 | if bssid_matches: 119 | bssid = bssid_matches.group(1) 120 | if 'Broadcast' not in bssid: 121 | network_macs.add(EUI(bssid)) 122 | 123 | # count data packets 124 | length_match = length_re.search(line) 125 | if length_match: 126 | length = int(length_match.group(1)) 127 | mac_matches = tcpdump_mac_re.findall(line) 128 | if mac_matches: 129 | macs = set([EUI(match[1]) for match in mac_matches]) 130 | leftover = macs - network_macs 131 | if len(leftover) < len(macs): 132 | for mac in leftover: 133 | data_totals[mac] += length 134 | client_macs.add(mac) 135 | 136 | if progress.n < progress.total: 137 | eprint('Sniffing finished early.') 138 | 139 | except subprocess.CalledProcessError: 140 | eprint('Error collecting packets.') 141 | raise 142 | except KeyboardInterrupt: 143 | pass 144 | 145 | totals_sorted = sorted(data_totals.items(), 146 | key=lambda x: x[1], 147 | reverse=True) 148 | 149 | eprint('Total of {} user(s)'.format(len(totals_sorted))) 150 | 151 | for mac, total in reversed(totals_sorted[:args.results]): 152 | mac.dialect = mac_unix_expanded 153 | if total > 0: 154 | print('{}\t{} bytes'.format(mac, total)) 155 | 156 | 157 | if __name__ == '__main__': 158 | from sys import argv 159 | 160 | try: 161 | main(argv) 162 | except KeyboardInterrupt: 163 | pass 164 | sys.exit() 165 | --------------------------------------------------------------------------------