├── README └── hoover.pl /README: -------------------------------------------------------------------------------- 1 | hoover.pl - Wi-Fi probe requests sniffer 2 | 3 | Original idea by David Nelissen (twitter.com/davidnelissen) 4 | Thank to him for allowing me to reuse the idea! 5 | 6 | This script scans for wireless probe requests and prints them out. 7 | Hereby you can see for which SSID's devices nearby are searching. 8 | 9 | Usage: hoover.pl --interface=wlan0 [--help] [--verbose] [--iwconfig-path=/sbin/iwconfig] [--ipconfig-path=/sbin/ifconfig] 10 | [--dumpfile=result.txt] 11 | Where: 12 | --interface : Specify the wireless interface to use 13 | --help : This help 14 | --verbose : Verbose output to STDOUT 15 | --ifconfig-path : Path to your ifconfig binary 16 | --iwconfig-path : Path to your iwconfig binary 17 | --tshark-path : Path to your tshark binary 18 | --dumpfile : Save found SSID's/MAC addresses in a flat file (SIGUSR1) 19 | -------------------------------------------------------------------------------- /hoover.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # hoover.pl - Wi-Fi probe requests sniffer 4 | # 5 | # Original idea by David Nelissen (twitter.com/davidnelissen) 6 | # Thank to him for allowing me to reuse the idea! 7 | # 8 | # This script scans for wireless probe requests and prints them out. 9 | # Hereby you can see for which SSID's devices nearby are searching. 10 | # 11 | # Copyright (c) 2012 David Nelissen & Xavier Mertens 12 | # All rights reserved. 13 | # 14 | # Redistribution and use in source and binary forms, with or without 15 | # modification, are permitted provided that the following conditions 16 | # are met: 17 | # 1. Redistributions of source code must retain the above copyright 18 | # notice, this list of conditions and the following disclaimer. 19 | # 2. Redistributions in binary form must reproduce the above copyright 20 | # notice, this list of conditions and the following disclaimer in the 21 | # documentation and/or other materials provided with the distribution. 22 | # 3. Neither the name of copyright holders nor the names of its 23 | # contributors may be used to endorse or promote products derived 24 | # from this software without specific prior written permission. 25 | # 26 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS 30 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | # POSSIBILITY OF SUCH DAMAGE. 37 | # 38 | # History 39 | # ------- 40 | # 2012/01/11 Created 41 | # 2015/06/09 Fix: root detection 42 | # 43 | 44 | use strict; 45 | use Getopt::Long; 46 | 47 | $SIG{USR1} = \&dumpNetworks; # Catch SIGINT to dump the detected networks 48 | $SIG{INT} = \&cleanKill; 49 | $SIG{KILL} = \&cleanKill; 50 | $SIG{TERM} = \&cleanKill; 51 | 52 | my $uniqueSSID = 0; #uniq ssid counter 53 | my %detectedSSID; # Detected network will be stored in a hash table 54 | # SSID, Seen packets, Last timestamp 55 | my $pid; 56 | my $help; 57 | my $verbose; 58 | my $interface; 59 | my $dumpFile; 60 | my $ifconfigPath = "/sbin/ifconfig"; 61 | my $iwconfigPath = "/sbin/iwconfig"; 62 | my $tsharkPath = "/usr/local/bin/tshark"; 63 | my $options = GetOptions( 64 | "verbose" => \$verbose, 65 | "help" => \$help, 66 | "interface=s" => \$interface, 67 | "ifconfig-path=s" => \$ifconfigPath, 68 | "iwconfig-path=s" => \$iwconfigPath, 69 | "tshark-path=s" => \$tsharkPath, 70 | "dumpfile=s" => \$dumpFile, 71 | ); 72 | 73 | if ($help) { 74 | print <<_HELP_; 75 | Usage: $0 --interface=wlan0 [--help] [--verbose] [--iwconfig-path=/sbin/iwconfig] [--ipconfig-path=/sbin/ifconfig] 76 | [--dumpfile=result.txt] 77 | Where: 78 | --interface : Specify the wireless interface to use 79 | --help : This help 80 | --verbose : Verbose output to STDOUT 81 | --ifconfig-path : Path to your ifconfig binary 82 | --iwconfig-path : Path to your iwconfig binary 83 | --tshark-path : Path to your tshark binary 84 | --dumpfile : Save found SSID's/MAC addresses in a flat file (SIGUSR1) 85 | _HELP_ 86 | exit 0; 87 | } 88 | 89 | # We must be run by root 90 | ($> ne 0) && die "$0 must be run by root!\n"; 91 | 92 | # We must have an interface to listen to 93 | (!$interface) && die "No wireless interface speficied!\n"; 94 | 95 | # Check ifconfig availability 96 | ( ! -x $ifconfigPath) && die "ifconfig tool not found!\n"; 97 | 98 | # Check iwconfig availability 99 | ( ! -x $iwconfigPath) && die "iwconfig tool not found!\n"; 100 | 101 | # Check tshark availability 102 | ( ! -x $tsharkPath) && die "tshark tool not available!\n"; 103 | 104 | # Configure wireless interface 105 | (system("$ifconfigPath $interface up")) && "Cannot initialize interface $interface!\n"; 106 | 107 | # Set interface in monitor mode 108 | (system("$iwconfigPath $interface mode monitor")) && die "Cannot set interface $interface in monitoring mode!\n"; 109 | 110 | # Create the child process to change wireless channels 111 | (!defined($pid = fork)) && die "Cannot fork child process!\n"; 112 | 113 | if ($pid) { 114 | # --------------------------------- 115 | # Parent process: run the main loop 116 | # --------------------------------- 117 | ($verbose) && print "!! Running with PID: $$ (child: $pid)\n"; 118 | open(TSHARK, "$tsharkPath -i $interface -n -l subtype probereq |") || die "Cannot spawn tshark process!\n"; 119 | while () { 120 | chomp; 121 | my $line = $_; 122 | chomp($line = $_); 123 | # Everything exept backslash (some probes contains the ssid in ascii, not usable) 124 | #if($line = m/\d+\.\d+ ([a-zA-Z0-9:]+).+SSID=([a-zA-ZÀ-ÿ0-9"\s\!\@\$\%\^\&\*\(\)\_\-\+\=\[\]\{\}\,\.\?\>\<]+)/) { 125 | if($line = m/\d+\.\d+ ([a-zA-Z0-9:_]+).+SSID=([a-zA-ZÀ-ÿ0-9"\s\!\@\$\%\^\&\*\(\)\_\-\+\=\[\]\{\}\,\.\?\>\<]+)/) { 126 | if($2 ne "Broadcast") { # Ignore broadcasts 127 | my $macAddress = $1; 128 | my $newKey = $2; 129 | print DEBUG "$macAddress : $newKey\n"; 130 | if (! $detectedSSID{$newKey}) 131 | { 132 | # New network found! 133 | my @newSSID = ( $newKey, # SSID 134 | 1, # First packet 135 | $macAddress, # MAC Address 136 | time()); # Seen now 137 | $detectedSSID{$newKey} = [ @newSSID ]; 138 | $uniqueSSID++; 139 | print "++ New probe request from $macAddress with SSID: $newKey [$uniqueSSID]\n"; 140 | } 141 | else 142 | { 143 | # Existing SSID found! 144 | $detectedSSID{$newKey}[1]++; # Increase packets counter 145 | $detectedSSID{$newKey}[2] = $macAddress; # MAC Address 146 | $detectedSSID{$newKey}[3] = time(); # Now 147 | ($verbose) && print "-- Probe seen before: $newKey [$uniqueSSID]\n"; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | else { 154 | # -------------------------------------------------- 155 | # Child process: Switch channels at regular interval 156 | # -------------------------------------------------- 157 | ($verbose) && print STDOUT "!! Switching wireless channel every 5\".\n"; 158 | while (1) { 159 | for (my $channel = 1; $channel <= 12; $channel++) { 160 | (system("$iwconfigPath $interface channel $channel")) && 161 | die "Cannot set interface channel.\n"; 162 | sleep(5); 163 | } 164 | } 165 | 166 | } 167 | 168 | sub dumpNetworks { 169 | my $i; 170 | my $key; 171 | print STDOUT "!! Dumping detected networks:\n"; 172 | print STDOUT "!! MAC Address SSID Count Last Seen\n"; 173 | print STDOUT "!! -------------------- ------------------------------ ---------- -------------------\n"; 174 | if ($dumpFile) { 175 | open(DUMP, ">$dumpFile") || die "Cannot write to $dumpFile (Error: $?)"; 176 | print DUMP "MAC Address SSID Count Last Seen\n"; 177 | print DUMP "-------------------- ------------------------------ ---------- -------------------\n"; 178 | } 179 | for $key ( keys %detectedSSID) 180 | { 181 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($detectedSSID{$key}[2]); 182 | my $lastSeen = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec); 183 | print STDOUT sprintf("!! %-20s %-30s %10s %-20s\n", $detectedSSID{$key}[2], 184 | $detectedSSID{$key}[0], $detectedSSID{$key}[1], $lastSeen); 185 | ($dumpFile) && print DUMP sprintf("%-20s %-30s %10s %-20s\n", 186 | $detectedSSID{$key}[2], $detectedSSID{$key}[0], 187 | $detectedSSID{$key}[1], $lastSeen); 188 | } 189 | print STDOUT "!! Total unique SSID: $uniqueSSID\n"; 190 | ($dumpFile) && print DUMP "Total unique SSID: $uniqueSSID\n"; 191 | close(DUMP); 192 | return; 193 | } 194 | 195 | sub cleanKill { 196 | if ($pid) { 197 | # Parent process: display information 198 | print "!! Received kill signal!\n"; 199 | kill 1, $pid; 200 | dumpNetworks; 201 | } 202 | exit 0; 203 | } 204 | --------------------------------------------------------------------------------