===== lk_wiclitatoo.py: Script para identificar todos los clientes usando redes wifi en tiempo real =====
El script en python [[http://www.layakk.com/tools/lk_wiclitatoo.py|lk_wiclitatoo.py]] se centra únicamente en obtener una lista de todos los dispositivos (impresoras, smartphones, laptops, etc) con wifi activado en nuestro alrededor + información referente a su configuración / uso. Permite conocer a qué redes se encuentra conectado o suele conectar un determinado dispositivo.
Se necesita activar el modo promiscuo de la tarjeta de red inalámbrica.
NOTA: Kismet puede obtener también la información de clientes que usan redes wifi, pero si solo necesitamos información sobre clientes, lk_wiclitatoo.py es mucho más amigable y útil a la hora de recolectar dicha información.
**Ejemplo de uso y salida de lk_wiclitatoo**.
python /home/busi/lk_wiclitatoo.py -h
usage: lk_wiclitatoo2.py [-h] [-v] [-a] -i INTERFACE [-r REFRESH_INTERVAL]
[-p INITIAL_PAUSE_SECONDS] [--nolog]
[--log_dir LOG_DIR]
[--log_filename_prefix LOG_FILENAME_PREFIX]
[-m VENDORS_TO_BE_IGNORED_REGEX]
[-n NETWORKS_OF_INTEREST_FILENAME]
[-e ADDITIONAL_ESSIDS_OF_INTEREST_REGEX]
[-q ESSIDS_TO_BE_IGNORED_REGEX]
[--coi_file_in COI_FILE_IN]
[--coi_file_out COI_FILE_OUT]
[-o OUI_TO_VENDORS_FILENAME]
ifconfig wlp2s0 down
iwconfig wlp2s0 mode monitor
ifconfig wlp2s0 up
python /home/busi/lk_wiclitatoo.py -i wlp2s0
Host: XXXXX. Now: 2015-09-09 20:23:03
========================================================================================================================================
CLIENT | VENDOR | LASTSEEN | R | BSSIDs -- ESSIDs | Probed_ESSIDs
========================================================================================================================================
e4:58:b8:70:b9:e7 | SamsungE | 20:09:14 | Pa | [>, c4:8e:1a:da:3b:4c] -- [] | ['any', 'ROBERTO']
14:b4:84:67:54:20 | SamsungE | 20:10:29 | P | [] -- [] | ['any']
80:13:82:b4:30:16 | HuaweiTe | 20:10:29 | Pa | [>, 74:85:2a:29:60:cd] -- [] | ['any']
e4:58:b8:e8:ca:d5 | SamsungE | 20:10:40 | Pa | [>, 9c:ad:97:c3:8a:2b] -- [] | ['any']
80:13:82:0d:ee:9a | HuaweiTe | 20:10:47 | P | [] -- [] | ['any']
b4:4b:d2:01:3f:db | Apple | 20:10:53 | P | [] -- [] | ['ROBERTO']
40:40:a7:4d:be:69 | SonyMobi | 20:10:58 | P | [] -- [] | ['any']
04:02:1f:a4:f3:89 | HuaweiTe | 20:11:05 | P | [] -- [] | ['any', 'VIVO-WIFI']
b8:5e:7b:7c:7d:6e | SamsungE | 20:13:20 | P | [] -- [] | ['LEOS', 'movistar']
18:21:95:66:21:60 | SamsungE | 20:15:00 | P | [] -- [] | ['any']
30:10:b3:00:4b:55 | LiteonTe | 20:15:08 | Pa | [>, 00:71:c2:15:3c:a0] -- [] | ['any']
a0:32:99:01:63:c7 | LenovoBe | 20:16:07 | P | [] -- [] | ['any']
f8:a9:d0:7f:1a:6e | LgElectr | 20:16:28 | P | [] -- [] | ['any']
1c:cb:99:73:76:1f | TctMobil | 20:17:13 | P | [] -- [] | ['any']
00:34:da:9c:f2:38 | LgElectr | 20:17:17 | Pa | [>, 90:f6:52:61:4f:2c] -- [] | ['any']
98:e7:f5:d5:25:82 | HuaweiTe | 20:17:21 | P | [] -- [] | ['any', 'VIVO-WIFI']
bc:e5:9f:5c:12:9c | Waterwor | 20:17:52 | P | [] -- [] | ['any']
34:bb:26:0b:e6:02 | Motorola | 20:18:05 | P | [] -- [] | ['any']
18:67:b0:7e:1a:33 | SamsungE | 20:18:05 | P | [] -- [] | ['any']
54:ef:92:8a:39:e4 | Shenzhen | 20:18:06 | P | [] -- [] | ['any']
80:6c:1b:dd:7e:38 | Motorola | 20:18:24 | P | [] -- [] | ['WLAN_5D85']
a0:cb:fd:16:df:4c | SamsungE | 20:18:38 | P | [] -- [] | ['MOVISTAR WIFI', 'any']
28:27:bf:af:1e:2a | SamsungE | 20:19:20 | P | [] -- [] | ['MOVISTAR WIFI', 'any']
1c:56:fe:0c:cc:5b | Motorola | 20:19:27 | P | [] -- [] | ['any']
48:d2:24:91:07:2d | LiteonTe | 20:19:41 | P | [] -- [] | ['CECILIA']
9c:65:b0:43:f4:b2 | SamsungE | 20:19:47 | P | [] -- [] | ['VILLENA1']
68:72:51:2a:5b:26 | Ubiquiti | 20:19:51 | Pa | [>, 68:72:51:22:07:3d] -- [] | ['SURQUINET 945748911']
40:f3:08:7e:4b:58 | MurataMa | 20:19:59 | P | [] -- [] | ['any']
f4:0e:22:6f:16:92 | SamsungE | 20:20:04 | Pa | [>, c4:8e:1a:da:3b:4c] -- [] | ['ROBERTO']
00:08:ca:22:bc:b7 | TwinhanT | 20:20:16 | P | [] -- [] | ['FAM.NC', 'any']
30:b5:c2:24:a0:62 | Tp-LinkT | 20:20:18 | P | [] -- [] | ['RAFAEL']
90:f6:52:6b:85:00 | Tp-LinkT | 20:20:26 | P | [] -- [] | ['MERKURIO RED MAS 936855600 ', 'any']
f8:cf:c5:83:64:63 | Motorola | 20:20:29 | P | [] -- [] | ['any', 'LUCIANA', 'ucsm_wifi_15', 'UGARTE', 'OLTURSA 2152', '.WIGO.Larcomar', 'PROYECTOR-02-PC8EkCl_ju09S6j', 'MALL AVENTURA LIMA03', 'MALL AVENTURA LIMA04']
64:eb:8c:21:31:bf | SeikoEps | 20:20:33 | P | [] -- [] | ['Olenka']
30:75:12:47:4c:db | SonyMobi | 20:20:34 | P | [] -- [] | ['AQP_ROJAS_GUERRA', 'AndroidAP', 'RSCR']
24:09:95:ba:fe:63 | HuaweiTe | 20:20:40 | P | [] -- [] | ['WLAN_660B', 'HOUSE', 'ARGUIAQP', 'SGC0654', 'WLAN_5272', 'KEN II *** 954729801']
64:27:37:10:33:5b | HonHaiPr | 20:20:56 | Pa | [>, 90:f6:52:61:4f:2c] -- [] | ['WIROSA']
5c:ac:4c:7e:7d:ed | HonHaiPr | 20:20:57 | P | [] -- [] | ['ENRIQUE', 'any']
d4:61:2e:d7:29:24 | HuaweiTe | 20:20:58 | P | [] -- [] | ['any']
80:6c:1b:d6:21:6f | Motorola | 20:21:01 | P | [] -- [] | ['any', 'RADWIN 1 942341750 ', 'HP-Print-b8-LaserJet Pro MFP', 'HP-Print-D0-Deskjet 4640 series', 'TP-LINK_27FFA1', 'MDCC Internet (Libre)', 'TP-LINK_780970', 'BypG-WFQ5MTk', 'TIM Wi-Fi', 'CLARO-WIFI', 'Personal EAP', 'HP-Print-4B-LaserJet 1102', 'Bq6N-Tmljb2xhcw', 'HP-Print-D7-LaserJet 1102']
98:2f:3c:de:19:09 | SichuanC | 20:21:02 | P | [] -- [] | ['any']
e8:de:27:ce:c8:4c | Tp-LinkT | 20:21:08 | P | [] -- [] | ['any']
04:c2:3e:8a:90:07 | HtcCorpo | 20:21:08 | P | [] -- [] | ['any']
bc:b1:f3:b3:9b:90 | SamsungE | 20:21:14 | P | [] -- [] | ['any']
8c:34:fd:4b:a8:cb | HuaweiTe | 20:21:36 | P | [] -- [] | ['WLAN_B74D', 'Wifi VIDOR1- 936229317-936841000', 'any']
d0:df:9a:2a:7b:1f | LiteonTe | 20:21:40 | P | [] -- [] | ['any']
24:4b:03:f4:d8:fa | SamsungE | 20:22:32 | P | [] -- [] | ['any']
a0:39:f7:1f:3e:42 | LgElectr | 20:22:36 | P | [] -- [] | ['any']
60:be:b5:a3:0c:0a | Motorola | 20:22:49 | Pa | [>, 60:02:92:51:e6:db] -- [] | ['SAGASTA', 'any', '__LUIS CARLOS__', 'CLUBVEA', 'TRASACACIA2', '(((G@BYNET4))) 957910126 488493', 'AQP_RED_936107019', 'STSOFT_E 993160909', 'MONEDEROWIFI.COM']
b4:4b:d2:02:37:37 | Apple | 20:22:53 | P | [] -- [] | ['Apolo2.5']
b4:74:43:ec:3e:0d | SamsungE | 20:22:59 | Pa | [>, 60:02:92:51:e6:db] -- [] | ['SAGASTA']
========================================================================================================================================
CLIENT | VENDOR | LASTSEEN | R | BSSIDs -- ESSIDs | Probed_ESSIDs
========================================================================================================================================
Host: XXXXX. Now: 2015-09-09 20:23:03
**Código de lk_wiclitatoo.py** (Python).
#!/usr/bin/env python
#########################
## lk_wiclitatoo.py :
##
## Wi-Fi Client Targeting Tool
##
## A tool that monitors Wi-Fi traffic and displays information
## about clients of interest in real time.
##
## Copyright (C) 2015 LAYAKK - www.layakk.com - @layakk
##
## This program 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.
##
## This program 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 this program. If not, see .
##
## PRERREQUISITES
## python-scapy
## wireshark (optional, for MAC to vendor name translation)
##
## USAGE
## See help (-h)
#########################
### Imports
import sys
from datetime import datetime
import re
import os
import resource
import platform
import argparse
import time
import fcntl, struct, socket
# Import scapy while ignoring warnings (to avoid annoying warning msg about ipv6)
tmpout = sys.stdout; tmperr = sys.stderr
sys.stdout = open('/dev/null', 'w'); sys.stderr = open('/dev/null', 'w')
from scapy.all import *
sys.stdout = tmpout; sys.stderr = tmperr
### Global variables
verbose = ""
anonymize = ""
interface = ""
refresh_interval_seconds = None
initial_pause_seconds = ""
logtofile = ""
logdir = ""
logfilenameprefix = ""
vendors_to_be_ignored_regex = ""
essids_to_be_ignored_regex = ""
additional_essids_of_interest_regex = ""
coi_file_in = ""
coi_file_out = ""
networks_of_interest_filename = ""
oui_to_vendors_filename = ""
clients = []
vendor = dict()
firstseen = dict()
lastseen = dict()
reasons = dict()
bssids = dict()
networks_probed = dict()
networks_active = dict()
ouis = dict()
essid_of_interest = dict() #(key=bssid of interest, value=essid_of_interest)
tmplist = []
timeoflastprintout = datetime.now()
timestamp = datetime.now()
faketimestamp = datetime(1900,1,1,0,0)
fieldsformat='{0:^17} | {1:^10} | {2:^8} | {3:^5} | {4:<28} -- {5:<28} | {6:<13}'
### Classes
### Functions
def create_argumentparser():
desc = 'lk_wiclitatoo.py (v1.0) - Wi-Fi clients targeting tool\n'
desc += 'Copyright (c) 2015 Layakk (www.layakk.com) (@layakk)'
parser=argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=desc)
parser.add_argument("-v", "--verbose", action='store_true', default=False)
parser.add_argument("-a", "--anonymize", action='store_true', default=False)
parser.add_argument("-i", "--interface", help='Interface to listen on (must alread be up and in monitor mode', required=True)
parser.add_argument("-r", "--refresh_interval", help='Refresh interval in seconds (default=10)', type=int, default=10)
parser.add_argument("-p", "--initial_pause_seconds", help='If this options is not specified, upon start wiclitatoo will display its configuration and wait for the ENTER key to be pressed. If specified, wiclitatoo will pause for the given number of seconds and continue without expecting any input from the user.', default="99999")
parser.add_argument("--nolog", help='Do not create a log file', action='store_true', default=False)
parser.add_argument("--log_dir", help='Directory for log files (default=/var/log/lk_wiclitatoo)', default="/var/log/lk_wiclitatoo")
parser.add_argument("--log_filename_prefix", help='Name prefix for the log file (default=lk_wiclitatoo). A timestamp and a ".$HOSTNAME" suffix will automatically be appended to it.', default="wiclitatoo")
parser.add_argument("-m", "--vendors_to_be_ignored_regex", help='Regular expression defining client vendors to be ignored (e.g: "^IntelCor$") (default="^NO-VENDOR-WILL-BE-IGNORED$", i.e. all vendors would be considered).', default="^NO-VENDOR-WILL-BE-IGNORED$")
parser.add_argument("-n", "--networks_of_interest_filename", help='File containing networks of interest. Each line must contain exactly one BSSID and one ESSID, separated by one white space. Both BSSID and ESSID must be indicated, but any of them may be bogus if unknown (e.g. "00:00:00:00:00:00 MYESSID"). If the file name does not end in ".$HOSTNAME", that suffix will automatically be appended to it. This option is complemented with the option "--additional_essids_of_interest_regex". If "--networks_of_interest_filename" is not specified, only "--additional_essids_of_interest_regex" will determine which ESSIDs are considered of interest. (default=none)', default="")
parser.add_argument("-e", "--additional_essids_of_interest_regex", help='Regular expression defining additional ESSIDs to be considered of interest, on top of those indicated in the networks-of-interest file (option "-n"), if any. Examples: "^WLAN_.*$" (ESSIDs begining with "WLAN_"), "^.*$" (any ESSID). Default: If a networks-of-interest is specified with the option "-n", the default value is "^NO-ADDITIONAL_ESSIDS_OF_INTEREST$"; however, if no networks-of-interest file is specified with the option "-n", the default value is "^.*$" (any ESSID).', default="^NO-ADDITIONAL_ESSIDS_OF_INTEREST$")
parser.add_argument("-q", "--essids_to_be_ignored_regex", help='Regular expression defining ESSIDs to be ignored (e.g: "^any$") (default="^NO-ESSID-BLACKLSIT$", i.e. no ESSID would be blacklisted). An ESSID matching essids_to_be_ignored_regex will always be ignored, no matter what.', default="^NO-ESSID-BLACKLIST$")
parser.add_argument("--coi_file_in", help='File containing clients of interest, one mac per line. New clients will be appended to the file. If the file name does not end in ".$HOSTNAME", that suffix will automatically be appended to it. (default=none)', default="")
parser.add_argument("--coi_file_out", help='Output file that will contain clients of interest, one mac per line. It will include old and new clients of interest. If the file name does not end in ".$HOSTNAME", that suffix will automatically be appended to it. When \"--coi_file_in\" is also specified, and the reserved file name \"idem\" is used as coi_file_out, wiclitatoo will append any new clients found of interest to the existing coi_file_in. (default=none)', default="")
parser.add_argument("-o", "--oui_to_vendors_filename", help='File containing the mapping between OUIs and the name of their corresponding vendors. Expected format: that of the "manuf" file distributed with wireshark (e.g. "/usr/share/wireshark/manuf"). (default="/usr/share/wireshark/manuf")', default="/usr/share/wireshark/manuf")
return parser
def process_args(args):
global verbose
global anonymize
global interface
global refresh_interval_seconds
global initial_pause_seconds
global logtofile
global logdir
global logfilenameprefix
global vendors_to_be_ignored_regex
global essids_to_be_ignored_regex
global additional_essids_of_interest_regex
global coi_file_in
global coi_file_out
global networks_of_interest_filename
global oui_to_vendors_filename
verbose = args.verbose
anonymize = args.anonymize
interface = args.interface
refresh_interval_seconds = args.refresh_interval
if (args.initial_pause_seconds == "99999"):
initial_pause_seconds = ""
else:
initial_pause_seconds = int(args.initial_pause_seconds)
logtofile = not args.nolog
logdir = args.log_dir
logfilenameprefix = args.log_filename_prefix
vendors_to_be_ignored_regex = args.vendors_to_be_ignored_regex
essids_to_be_ignored_regex = args.essids_to_be_ignored_regex
coi_file_in = amend_filename_if_needed(args.coi_file_in)
if (args.coi_file_out == "idem"):
if (coi_file_in == ""):
print ' *** ERROR *** coi_file_out set to special file name "idem", but no coi_file_in was specified. Exiting.'
sys.exit(-1)
else:
coi_file_out = coi_file_in
else:
coi_file_out = amend_filename_if_needed(args.coi_file_out)
networks_of_interest_filename = amend_filename_if_needed(args.networks_of_interest_filename)
if (args.networks_of_interest_filename == "" and args.additional_essids_of_interest_regex == "^NO-ADDITIONAL_ESSIDS_OF_INTEREST$"):
additional_essids_of_interest_regex = "^.*$"
else:
additional_essids_of_interest_regex = args.additional_essids_of_interest_regex
oui_to_vendors_filename = args.oui_to_vendors_filename
def report_configuration():
printandlog('--------------------------------------------------')
printandlog(" Host: {0}. Now: {1}".format(platform.node(), timestamp.strftime('%Y-%m-%d %H:%M:%S')))
printandlog('--------------------------------------------------')
printandlog(' lk_wiclitatoo configuration ')
printandlog('--------------------------------------------------')
printandlog('Verbose: {0}'.format(verbose))
printandlog('Anonymization: {0}'.format(anonymize))
printandlog('Refresh interval (seconds): {0}'.format(refresh_interval_seconds))
if (initial_pause_seconds != ""):
tmptxt = initial_pause_seconds
else:
tmptxt = "(Wait until ENTER is pressed)"
printandlog('Initial pause (seconds): {0}'.format(tmptxt))
printandlog('Interface: {0}'.format(interface))
printandlog('Logging to file: {0}'.format(logtofile))
if (logtofile == True):
printandlog('Log file: {0}'.format(logfullpath))
printandlog('Vendors to be ignored (regex): "{0}"'.format(vendors_to_be_ignored_regex))
printandlog('Additional ESSIDs of interest (regex): "{0}"'.format(additional_essids_of_interest_regex))
printandlog('ESSIDs to be ignored (regex): "{0}"'.format(essids_to_be_ignored_regex))
if (coi_file_in != ""):
tmptxt = coi_file_in
else:
tmptxt = "(none)"
printandlog('Clients-of-interest input file: {0}'.format(tmptxt))
if (coi_file_out != ""):
tmptxt = coi_file_out
else:
tmptxt = "(none)"
printandlog('Clients-of-interest output file: {0}'.format(tmptxt))
if (networks_of_interest_filename != ""):
tmptxt = networks_of_interest_filename
else:
tmptxt = "(none)"
printandlog('Networks of interest file: {0}'.format(tmptxt))
printandlog('OUI to vendor names file: {0}'.format(oui_to_vendors_filename))
printandlog('--------------------------------------------------')
printandlog('')
def report_networks_of_interest():
printandlog("Networks of interest:")
printandlog("--------------------------------------------------")
for bssid in essid_of_interest.keys():
printandlog("{0} {1}".format(anonymize_mac(bssid), essid_of_interest[bssid]))
printandlog("--------------------------------------------------")
printandlog("")
def report_clients_of_interest():
printandlog("Clients of interest:")
printandlog("--------------------------------------------------")
for mac in clients:
printandlog("{0} ({1})".format(anonymize_mac(mac), vendor[mac]))
printandlog("--------------------------------------------------")
printandlog("")
def printandlog(text):
print text
if logtofile:
logfile.write(text)
logfile.write("\n")
def verboseprint(text):
if verbose:
printandlog(text)
sys.stdout.flush()
logfile.flush()
def anonymize_mac(mac):
if anonymize:
return re.sub(':([0-9a-f]{2}:){4}', ':**:**:**:**:', mac)
else:
return mac
def bssids_list2str(b):
return "[" + ", ".join( anonymize_mac(str(x)) for x in b) + "]"
def printclient(c):
# c is a client (mac address)
printandlog(fieldsformat.format(anonymize_mac(c), vendor[c], lastseen[c].strftime('%H:%M:%S'), reasons[c], bssids_list2str(bssids[c]), networks_active[c], networks_probed[c]))
def printheader():
printandlog('========================================================================================================================================')
printandlog(fieldsformat.format("CLIENT", "VENDOR", "LASTSEEN", "R", "BSSIDs", "ESSIDs", "Probed_ESSIDs"))
printandlog('========================================================================================================================================')
def printhostandtime():
printandlog("Host: %s. Now: %s" % (platform.node(), timestamp.strftime('%Y-%m-%d %H:%M:%S')))
def printclients(cl):
# cl is a list of clients (mac addresses)
printhostandtime()
printheader()
for c in cl:
if (reasons[c] != "."):
printclient(c)
printheader()
printhostandtime()
printandlog("\n")
sys.stdout.flush()
logfile.flush()
def amend_filename_if_needed(filename):
if (filename == ""):
return filename
hostname = os.uname()[1]
# Check file name
regexp = '.*[.]{1}' + hostname + '$'
if not re.match(regexp, filename):
newfilename = filename + "." + hostname
else:
newfilename = filename
return newfilename
def file_exists(filename):
return os.path.isfile(filename)
def pause():
if (initial_pause_seconds == ""):
try:
input("Press ENTER to continue...")
except SyntaxError:
printandlog('...done. Continuing...')
pass
elif (initial_pause_seconds >= 0):
printandlog('Pausing for {0} seconds...'.format(initial_pause_seconds))
time.sleep(initial_pause_seconds)
printandlog('...done. Continuing...')
def load_ouis():
if not os.path.isfile(oui_to_vendors_filename):
printandlog(' *** WARNING *** Could not open OUI file: "{0}". Execution will continue, but vendors will not be identified.\n'.format(oui_to_vendors_filename))
return
with open(oui_to_vendors_filename, 'r') as f:
tmplist = list(f.read().splitlines())
tmplist = [ t for t in tmplist if re.match("^[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}\t", t.lower())]
tmplist = [ t.split("\t",1) for t in tmplist ]
for t in tmplist:
oui = t[0].lower()
manuf = (t[1].split())[0]
ouis[oui] = manuf
#printandlog("OUI: %s ; MANUFACTURER: %s" %(oui, manuf))
def load_networks_of_interest():
if (networks_of_interest_filename == ""):
return 0
if not os.path.isfile(networks_of_interest_filename):
printandlog(' *** WARNING *** Could not open networks-of-interest file: "{0}". Execution will continue, but the list of networks of interest will be empty.\n'.format(networks_of_interest_filename))
return
with open(networks_of_interest_filename, 'r') as f:
tmplist = list(f.read().splitlines())
for line in tmplist:
tmpbssid = line.split()[0].lower()
tmpessid = line.split()[1]
if tmpbssid not in essid_of_interest.keys():
essid_of_interest[tmpbssid]=tmpessid
verboseprint('Marking BSSID "{0}", ESSID "{1}" as of interest'.format(anonymize_mac(tmpbssid), essid_of_interest[tmpbssid]))
def create_file(filename):
if os.path.isfile(filename):
printandlog(' *** WARNING *** Was asked to create file "{0}", but file already exists. Skipping file creation.'.format(filename))
elif os.path.exists(filename):
printandlog(' *** ERROR *** Was asked to create file "{0}", but a directory (or something else other than a file) seems to exist with that name. Skipping file creation and exiting.'.format(filename))
sys.exit(-1)
else:
basedir = os.path.dirname(filename)
if basedir != "" and not os.path.exists(basedir):
os.makedirs(basedir)
f = open(filename, 'w')
f.close()
def load_clients_of_interest():
if (coi_file_in == ""):
return 0
if not os.path.isfile(coi_file_in):
printandlog(' *** ERROR *** Could not open clients-of-interest input file specified: "{0}". Exiting.\n'.format(coi_file_in))
sys.exit(-1)
with open(coi_file_in, 'r') as f:
tmplist = list(f.read().splitlines())
for mac in tmplist:
if ((not should_client_be_ignored(mac)) and (mac not in clients)):
verboseprint('Marking client "{0}" as of interest'.format(anonymize_mac(mac)))
initclient(mac)
reasons[mac]+="." # client is of interest because it was already identified as such in the past
def get_vendor(mac):
oui = mac[0:8].lower()
if oui in ouis.keys():
tmpvendor = ouis[oui]
else:
tmpvendor = "--------"
return tmpvendor
def should_client_be_ignored(mac):
answer = False
if re.match(vendors_to_be_ignored_regex, get_vendor(mac)):
verboseprint('Ignoring client {0}, because its vendor ({1}) is to be ignored.'.format(anonymize_mac(mac), get_vendor(mac)))
answer = True
return answer
def should_essid_be_ignored(essid):
answer = False
if re.match(essids_to_be_ignored_regex, essid):
verboseprint('Ignoring SSID {0}, because it matches essids_to_be_ignored ({1}).'.format(essid, essids_to_be_ignored_regex))
answer = True
return answer
def initclient(mac):
verboseprint('Adding client: {0} ({1})'.format(anonymize_mac(mac), get_vendor(mac)))
clients.append(mac)
vendor[mac] = get_vendor(mac)
firstseen[mac] = timestamp
lastseen[mac] = timestamp
reasons[mac] = ""
bssids[mac] = []
networks_active[mac] = []
networks_probed[mac] = []
if (coi_file_out != ""):
with open(coi_file_out, 'a') as f:
f.write(mac + "\n")
verboseprint("Adding client {0} to output coi file ({1})\n".format(anonymize_mac(mac), coi_file_out))
def check_interface():
# Check if interface is in monitor mode
retvalue = os.system("iwconfig %s | grep \"Monitor\" > /dev/null" % interface)
if retvalue != 0:
print "Error: The interface (%s) is not in monitor mode. Exiting." % interface
sys.exit(-1)
# Check if interface is up
tempsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ioctlresult = fcntl.ioctl(tempsocket.fileno(), 0x8913, interface + '\0'*256)
flags, = struct.unpack('H', ioctlresult[16:18])
if (flags & 1) == 0:
print "Error: The interface (%s) is down. Exiting." % interface
sys.exit(-1)
def sniffmgmt(p):
global timeoflastprintout
global tmplist
global timestamp
global clients
global vendor
del tmplist[:]
#printandlog('Memory usage: %s (kb)' %resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
timestamp = datetime.now()
# Probe request?
if p.haslayer(Dot11) and p.haslayer(Dot11ProbeReq):
mac = p[Dot11].addr2
ssid = p[Dot11Elt].info
if ssid == "":
ssid="any"
# Check mac and ssid for sanity
if not re.match("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", mac.lower()):
printandlog("INFO: Discarding probe request because of ill formatted MAC")
return
if not re.match("^[ -~]+$", ssid):
printandlog("INFO: Discarding probe request because of ill formatted SSID")
return
verboseprint("%s : Client %s is probing for %s" %(str(timestamp), anonymize_mac(mac), ssid))
# Client is probing for an ESSID of interest?
if ((ssid in essid_of_interest.values() or re.match(additional_essids_of_interest_regex, ssid)) and not should_essid_be_ignored(ssid)):
verboseprint('That is an ESSID of interest.')
# New client?
if mac not in clients:
if should_client_be_ignored(mac):
return
else:
initclient(mac)
# First probe?
if not re.match(".*P.*", reasons[mac]): # P = Probe
reasons[mac]+="P"
# New probed SSID?
tmplist = networks_probed[mac][:]
if ssid not in tmplist:
tmplist.append(ssid)
networks_probed[mac] = tmplist[:]
# Update last-seen timestamp
lastseen[mac] = timestamp
# Client is of interest anyways? (though it is probing for a not-of-interest essid)
elif mac in clients:
verboseprint('That is a client of interest, though probing for a not-of-interest essid.')
# First probe?
if not re.match(".*p.*", reasons[mac]): # p = probe for not-of-interest essid
reasons[mac]+="p"
# New probed SSID?
tmplist = networks_probed[mac][:]
if ssid not in tmplist:
tmplist.append(">")
tmplist.append(ssid)
networks_probed[mac] = tmplist[:]
# Update last-seen timestamp
lastseen[mac] = timestamp
# Active client? (not probe request and to-DS and not from-DS)
elif p.haslayer(Dot11) and (p.FCfield & 0x03 == 0x01): #to-DS and not from-DS?
# Note:
# addr1 = receiver
# addr2 = transmitter
# addr3 = either original source or intended destination
# addr4 = final source when frame is both tx and rx on a wireless DS
# In ToDS-No_FromDS:
# receiver = BSSID
# transmitter = client
bssid = p[Dot11].addr1
mac = p[Dot11].addr2
verboseprint('{0} : Client {1} is talking to BSSID {2}'.format(str(timestamp), anonymize_mac(mac), anonymize_mac(bssid)))
# Client is talking to AP of interest?
if bssid in essid_of_interest.keys():
verboseprint('That is a BSSID of interest.')
# New active client?
if mac not in clients:
if should_client_be_ignored(mac):
return
else:
initclient(mac)
# First time it is active?
if not re.match(".*A.*", reasons[mac]): # A = Active with a bssid of interest
reasons[mac]+="A"
# New BSSID?
tmplist = bssids[mac][:]
if bssid not in tmplist:
tmplist.append(bssid)
bssids[mac] = tmplist[:]
# New ESSID?
tmplist = networks_active[mac][:]
if essid_of_interest[bssid] not in tmplist:
tmplist.append(essid_of_interest[bssid])
networks_active[mac] = tmplist[:]
# Update last-seen timestamp
lastseen[mac] = timestamp
# Client is of interest anyways? (though talking to a not-of-interest AP)
elif mac in clients:
verboseprint('That is a client of interest, though talking to a not-of-interest bssid.')
# First time it is active?
if not re.match(".*a.*", reasons[mac]): # a = active, though with a not-of-interest bssid
reasons[mac]+="a"
# New BSSID?
tmplist = bssids[mac][:]
if bssid not in tmplist:
tmplist.append(">")
tmplist.append(bssid)
bssids[mac] = tmplist[:]
# Print updated list of interesting clients, from time to time
elapsedTime = timestamp - timeoflastprintout
if ( elapsedTime.seconds >= refresh_interval_seconds ):
timeoflastprintout = timestamp
os.system('clear')
sorted_clients = sorted(lastseen, key=lastseen.get, reverse=False)
printclients(sorted_clients)
verboseprint("\n\n")
### main
# Process arguments
aparser = create_argumentparser()
args = aparser.parse_args()
process_args(args)
#exit()
# Check interface
check_interface()
# Create logfile
if not os.path.isdir(logdir):
os.makedirs(logdir)
logfullpath = amend_filename_if_needed(logdir + '/' + logfilenameprefix + '-' +
datetime.now().strftime('%Y-%m-%d--%H-%M-%S') +
'.log' + '.' + platform.node())
logfile = open(logfullpath, "w")
# Report configuration
report_configuration()
# Load known vendors
load_ouis()
# Load networks of interest
load_networks_of_interest()
# Load clients of interest (using a fake timestamp)
tmp = timestamp
timestamp = faketimestamp
load_clients_of_interest()
timestamp = tmp
# Create coi_file_out if needed
if (coi_file_out != "" and coi_file_out != coi_file_in):
create_file(coi_file_out)
# Report networks and clients of interest
report_networks_of_interest()
report_clients_of_interest()
# Flush output
sys.stdout.flush()
logfile.flush()
# Initial pause
pause()
# Go!
sniff(iface=interface, prn=sniffmgmt, store=0)