Calculer les statistiques sur les fabricants de puces Wi-Fi avec du XML généré par Kismet
Présentation de Kismet
Kismet est un est un sniffeur Wi-Fi. Il permet d'enregistrer en temps réel des paquets Wi-Fi dans différents formats (dont le texte brute, pcap, et XML). C'est un logiciel libre et open-source.
Sous au moins Debian,
Ubuntu et Trisquel GNU/Linux,
il peut être installé avec APT
via le paquet kismet,
par exemple via la commande
apt install kismet
.
Puisque c'est un logiciel libre, vous pouvez bien entendu récupérer les sources.
Vous pouvez le faire dans un terminal avec
git clone https://www.kismetwireless.net/git/kismet.git
.
Vous pourrez ensuite compiler le code C++.
Identification du fabricant / constructeur
La méthode la plus simple pour identifier un fabricant / constructeur est d'utiliser l'adresse MAC des paquets Wi-Fi. En effet, l'adresse MAC est un identifiant supposé unique d'une interface matérielle. Cela peut poser un problème de vie privée si on n'a pas confiance dans son réseau local, ou plus simplement à chaque fois que l'on envoie des probe requests. Heureusement, il est possible de changer logiciellement d'adresse MAC. Cela peut être fait de manière automatique, par exemple avec macchanger.
Les 3 premiers octets (d'une adresse MAC) correspondent à l'OUI (Organizationally Unique Identifier). Cette OUI permet de connaitre le constructeur de la carte. Cette information peut être utilisée à mauvais escient, par exemple pour exploiter des failles d'un pilote répandu (que l'on connait). On peut aussi s'en servir pour savoir quel type de population fréquente un lieu (en fonction du prix du matériel, de l'aspect symbolique de la marque, etc.).
À partir de Android 6.0, l'adresse MAC est aléatoire. Il en est de même à partir de Apple iOS 8.
Exemple de script
#!/usr/bin/env python2
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided this notice is
# preserved. This file is offered as-is, without any warranty.
# Names of contributors must not be used to endorse or promote products
# derived from this file without specific prior written permission.
from sys import argv, stdout, stderr, exit
from os.path import isfile, getsize as file_size
import libxml2
from json import dumps as json_dumps
try:
from macpy import Mac
except:
try:
from Mac import Mac
except:
stderr.write("git clone https://github.com/hustcc/mac.py && todo\n")
stderr.write("or\n")
stderr.write("pip install mac.py\n")
mac_addresses = dict()
manufacturers = dict()
def mac_addresses_add(a_mac_address):
if a_mac_address is None:
return False
if a_mac_address in mac_addresses:
mac_addresses[a_mac_address] += 1
else:
mac_addresses[a_mac_address] = 1
return True
def fill_mac_addresses_with_xml_channel_node(node):
if node is None or len(node.content) == 0:
return False
mac_addresses_add(node.content)
return True
def fill_mac_addresses_with_xml_channel_nodes(nodes):
nb = 0
for node in nodes:
if fill_mac_addresses_with_xml_channel_node(node):
nb += 1
return nb
def find_xml_mac_address_nodes_with_xpath_context(a_xpath_context,
network = True,
client = True):
XPATH_STR_NETWORK = "//wireless-network/BSSID"
XPATH_STR_CLIENT = "//wireless-client/client-mac"
xpath_str = None
if network and client:
xpath_str = XPATH_STR_CLIENT +" | "+ XPATH_STR_NETWORK
elif not network and client:
xpath_str = XPATH_STR_CLIENT
elif network and not client:
xpath_str = XPATH_STR_NETWORK
else:
return 0
nodes = a_xpath_context.xpathEval(xpath_str)
return nodes
def fill_mac_addresses_with_xpath_context(a_xpath_context,
network = True, client = True):
nodes = find_xml_mac_address_nodes_with_xpath_context(a_xpath_context,
network, client)
return fill_mac_addresses_with_xml_channel_nodes(nodes)
def fill_mac_addresses_with_xml_document(a_document,
network = True, client = True):
a_xpath_context = a_document.xpathNewContext()
if a_xpath_context is None:
return -1
nb = fill_mac_addresses_with_xpath_context(a_xpath_context, network, client)
a_xpath_context.xpathFreeContext()
return nb
def fill_mac_addresses_with_xml_document_path(a_path,
network = True, client = True):
a_document = libxml2.parseFile(a_path)
if a_document is None:
return -1
nb = fill_mac_addresses_with_xml_document(a_document, network, client)
a_document.freeDoc()
return nb
def fill_mac_addresses_with_xml_document_path_to_check(a_path,
network = True,
client = True):
if len(a_path) == '':
stderr.write("Give a not empty XML file path as an argument\n")
exit(1)
if not isfile(a_path):
stderr.write(a_path +" is not a file\n")
exit(1)
if file_size(a_path) == 0:
stderr.write(a_path +" is empty\n")
exit(1)
return fill_mac_addresses_with_xml_document_path(a_path, network, client)
def manufacturers_add(a_manufacturer):
if a_manufacturer is None:
return False
if a_manufacturer in manufacturers:
manufacturers[a_manufacturer] += 1
else:
manufacturers[a_manufacturer] = 1
return True
def fill_manufacturers_with_mac_addresses():
nb = 0
a_mac_parser = Mac()
for a_mac_address in mac_addresses:
a_manufacturer = a_mac_parser.search(a_mac_address)
if a_manufacturer is None:
continue
a_manufacturer_company = None
if "company" in a_manufacturer:
a_manufacturer_company = a_manufacturer["company"]
elif "com" in a_manufacturer:
a_manufacturer_company = a_manufacturer["com"]
if(a_manufacturer_company is not None and
a_manufacturer_company != "" and
manufacturers_add(a_manufacturer_company)):
nb += 1
return nb
def print_mac_addresses_as_json(a_writer = stdout):
if len(manufacturers) > 0:
a_writer.write("[\n")
is_first = True
for a_mac_address in mac_addresses:
if is_first:
is_first = False
else:
a_writer.write(",")
a_writer.write("\n")
a_writer.write(" {\n")
a_writer.write(" %s: %s,\n" %
('"mac"',
json_dumps(a_mac_address)))
a_writer.write(" %s: %s\n" %
('"count"',
'"'+ str(mac_addresses[a_mac_address]) +'"'))
a_writer.write(" }")
a_writer.write("\n]\n")
def print_mac_addresses(output_format):
if output_format is None:
return False
output_format = output_format.strip().lower()
if output_format == "json":
print_mac_addresses_as_json()
return True
def print_manufacturers_as_json(a_writer = stdout):
if len(manufacturers) > 0:
a_writer.write("[\n")
is_first = True
for a_manufacturer in manufacturers:
if is_first:
is_first = False
else:
a_writer.write(",")
a_writer.write("\n")
a_writer.write(" {\n")
a_writer.write(" %s: %s,\n" %
('"name"',
json_dumps(a_manufacturer)))
a_writer.write(" %s: %s\n" %
('"count"',
'"'+ str(manufacturers[a_manufacturer]) +'"'))
a_writer.write(" }")
a_writer.write("\n]\n")
def print_manufacturers(output_format):
if output_format is None:
return False
output_format = output_format.strip().lower()
if output_format == "json":
print_manufacturers_as_json()
return True
stderr.write(output_format +" is not a managed format\n")
return False
def print_help(a_writer = stdout):
a_writer.write("Give a XML file path as an argument\n")
def main():
if len(argv) < 2:
print_help(stderr)
exit(1)
network = True
client = True
output_format = "json"
what_to_print = "manufacturers"
for arg in argv[1:]:
if arg == "--help":
print_help(stdout)
exit(0)
elif arg == "--client":
client = True
elif arg == "--no-client":
client = False
elif arg == "--network":
network = True
elif arg == "--no-network":
network = False
elif arg.startswith("--output-format="):
potential_output_format = arg[len("--output-format="):]
if len(potential_output_format) == 0:
stderr.write("Put a format just after (without space or tab)"+
" --output-format=\n")
exit(1)
potential_output_format = potential_output_format.strip().lower()
if not potential_output_format in ("json"):
stderr.write(potential_output_format +" is not a managed format\n")
exit(1)
output_format = potential_output_format
elif arg == "--print-manufacturers":
what_to_print = "manufacturers"
elif arg in ("--print-mac", "--print-mac-addr", "--print-mac-addresses"):
what_to_print = "mac"
else:
fill_mac_addresses_with_xml_document_path_to_check(arg,
network, client)
fill_manufacturers_with_mac_addresses()
try:
if what_to_print == "mac":
print_mac_addresses(output_format)
elif what_to_print == "manufacturers":
print_manufacturers(output_format)
else:
stderr.write(what_to_print +" is not something that can be printed.\n")
exit(1)
except IOError as e:
if not "Broken pipe" in str(e):
stderr.write(str(e) +"\n")
if __name__ == '__main__':
main()