Calcul de statistiques sur les canaux Wi-Fi avec du XML généré par Kismet
Une carte Wi-Fi peut utiliser différentes fréquences. Il existe des canaux Wi-Fi qui sont des fréquences autorisées (selon la juridiction) et des numéros simples associés.
On peut chercher à savoir quel est le canal le plus utilisé. Cette information peut être utilisée pour intercepter le maximum de paquets Wi-Fi en sniffant le canal le plus utilisé. Mais on peut aussi s'en servir pour se prémunir de ce genre d'attaques "intelligentes" en faisant utiliser le canal le moins susceptible d'être intercepté à un point d'accès Wi-Fi que l'on contrôle.
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
.
ArchLinux propose aussi un paquet se nommant 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++.
Langage Python et manipulation du XML
Python a plusieurs bibliothèques pour manipuler du XML. Il y a par exemple libxml2 qui se base sur une bibliothèque écrite en langage C, ce qui lui permet d'être rapide.
Identifier le canal Wi-Fi utilisé avec le XML généré par Kismet
Chaque balise wireless-client
ou wireless-network
peut contenir une balise channel
.
La valeur de la balise channel
est le numéro du canal Wi-Fi.
La recherche des balises peut se faire en parcourant via SAX ou en créant puis parcourant un arbre DOM. Pour se faciliter la tâche, XPath peut être utilisé.
Pour obtenir le canal de chaque paquet,
l'expression XPath 1.0 suivante peut être utilisée :
//channel
.
Pour obtenir le canal de chaque paquet des routeurs Wi-Fi,
l'expression XPath 1.0 suivante peut être utilisée :
//wireless-network/channel
.
Pour obtenir le canal de chaque paquet des clients Wi-Fi,
l'expression XPath 1.0 suivante peut être utilisée :
//wireless-client/channel
.
Exemple de script
Script de base
#!/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
channels = dict()
def channels_add(a_channel):
a_channel_uint = int(a_channel)
if a_channel_uint in channels:
channels[a_channel_uint] += 1
else:
channels[a_channel_uint] = 1
def fill_channels_with_xml_channel_node(node):
channels_add(node.content)
def fill_channels_with_xml_channel_nodes(nodes):
nb = 0
for node in nodes:
fill_channels_with_xml_channel_node(node)
nb += 1
return nb
def find_xml_channel_nodes_with_xpath_context(a_xpath_context):
nodes = a_xpath_context.xpathEval("//channel")
return nodes
def fill_channels_with_xpath_context(a_xpath_context):
nodes = find_xml_channel_nodes_with_xpath_context(a_xpath_context)
return fill_channels_with_xml_channel_nodes(nodes)
def fill_channels_with_xml_document(a_document):
a_xpath_context = a_document.xpathNewContext()
nb = fill_channels_with_xpath_context(a_xpath_context)
a_xpath_context.xpathFreeContext()
return nb
def fill_channels_with_xml_document_path(a_path):
a_document = libxml2.parseFile(a_path)
nb = fill_channels_with_xml_document(a_document)
a_document.freeDoc()
return nb
def get_channels_sum():
return sum(channels.values())
def print_channels(a_writer = stdout):
the_sum = get_channels_sum()
the_sum_less_0 = the_sum
if 0 in channels:
the_sum_less_0 -= channels[0]
for channel in channels:
value = channels[channel]
percentage = float(value) / float(the_sum)
percentage_less_0 = float(value) / float(the_sum_less_0)
a_writer.write("%2u -> %6u (%0.3f)" %
(channel, value, percentage))
if channel == 0:
a_writer.write(" (0 == unknow)")
else:
a_writer.write(" (%0.3f)" % percentage_less_0)
a_writer.write("\n")
def main():
if len(argv) < 2:
exit(1)
for arg in argv[1:]:
fill_channels_with_xml_document_path(arg)
print_channels()
if __name__ == '__main__':
main()
Script paramètrable avec gestion des erreurs
#!/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
channels = dict()
def channels_add(a_channel):
if a_channel is None:
return False
a_channel_uint = -1
try:
a_channel_uint = int(a_channel)
except ValueError:
return False
if a_channel_uint < 0:
return False
if a_channel_uint in channels:
channels[a_channel_uint] += 1
else:
channels[a_channel_uint] = 1
return True
def fill_channels_with_xml_channel_node(node):
if node is None or len(node.content) == 0:
return False
return channels_add(node.content)
def fill_channels_with_xml_channel_nodes(nodes):
nb = 0
for node in nodes:
if fill_channels_with_xml_channel_node(node):
nb += 1
return nb
def find_xml_channel_nodes_with_xpath_context(a_xpath_context,
network = True, client = True):
xpath_str = None
if network and client:
xpath_str = "//channel"
elif not network and client:
xpath_str = "//wireless-client/channel"
elif network and not client:
xpath_str = "//wireless-network/channel"
else:
return 0
nodes = a_xpath_context.xpathEval(xpath_str)
return nodes
def fill_channels_with_xpath_context(a_xpath_context,
network = True, client = True):
nodes = find_xml_channel_nodes_with_xpath_context(a_xpath_context,
network, client)
return fill_channels_with_xml_channel_nodes(nodes)
def fill_channels_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_channels_with_xpath_context(a_xpath_context, network, client)
a_xpath_context.xpathFreeContext()
return nb
def fill_channels_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_channels_with_xml_document(a_document, network, client)
a_document.freeDoc()
return nb
def fill_channels_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_channels_with_xml_document_path(a_path, network, client)
def get_channels_sum():
return sum(channels.values())
def print_channels(a_writer = stdout):
the_sum = get_channels_sum()
the_sum_less_0 = the_sum
if 0 in channels:
the_sum_less_0 -= channels[0]
for channel in channels:
value = channels[channel]
percentage = float(value) / float(the_sum)
percentage_less_0 = float(value) / float(the_sum_less_0)
a_writer.write("%2u -> %6u (%0.3f)" %
(channel, value, percentage))
if channel == 0:
a_writer.write(" (0 == unknow)")
else:
a_writer.write(" (%0.3f)" % percentage_less_0)
a_writer.write("\n")
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
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
else:
fill_channels_with_xml_document_path_to_check(arg, network, client)
print_channels()
if __name__ == '__main__':
main()