Calculer les statistiques sur le chiffrement utilisé pour le Wi-Fi avec du XML généré par Kismet

Par curiosité, on peut s’interroger sur l’adoption des méthodes de sécurisation du Wi-Fi. Cela peut servir pour savoir s’il sera facile d’attaquer des réseaux Wi-Fi dans une certaine zone. On peut en faire un usage offensif : attaquer les réseaux Wi-Fi mal sécurisés, mais c'est probablement illégal dans la majorité des juridictions (à moins d'avoir l'autorisation du gestionnaire du réseau). On peut également en faire un usage préventif : prévenir les personnes, afin d'éviter de potentiels futurs problèmes.

Présentation de Kismet


Logo 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 chiffrement utilisé

Pour identifier le chiffrement utilisé, on peut se baser sur les beacons et/ou les probes requests.

Identifier le chiffrement utilisé avec du XML généré par Kismet

Il faut s'intéresser aux balises wireless-network. Celles-ci peuvent contenir des balises BSSID, ssid et essid. Chacune de ces balises peut contenir une ou plusieurs balises encryption. La valeur contenue dans une balise encryption est le chiffrement utilisé sous forme de texte. Parmi les valeurs possibles, il y a : None, WEP, WPA+TKIP, WPA+PSK, et WPA+AES-CCM.

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é.

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


WIRELESS_NETWORK_SRC_PROBE  = 0
WIRELESS_NETWORK_SRC_BEACON = 1
WIRELESS_NETWORK_SRC_ALL    = 9

essid_encryption_methods = dict()
bssid_encryption_methods = dict()


def add_encryption_methods_to_dict(a_dict, key, encryption_methods):
    if key not in a_dict:
        a_dict[key] = set()
    
    for encryption_method in encryption_methods:
        a_dict[key].add(encryption_method)

def get_encryption_methods_from_network_node(network):
    encryption_nodes = network.xpathEval(".//encryption")
    encryption_methods = set()
    for encryption_node in encryption_nodes:
        encryption_methods.add(encryption_node.content)
    return encryption_methods

def fill_dict_with_xpath_key(a_dict, network_node, xpath_key, encryption_methods):
    nodes = network_node.xpathEval(xpath_key)
    if len(nodes) > 0:
        node = nodes[0]
        key = node.content
        add_encryption_methods_to_dict(a_dict,
                                       key,
                                       encryption_methods)

def fill_dicts_with_xml_document(a_document, src_type = WIRELESS_NETWORK_SRC_ALL):
    a_xpath_context = a_document.xpathNewContext()
    
    xpath_str = None
    if src_type == WIRELESS_NETWORK_SRC_PROBE:
        xpath_str = "//wireless-network[ .//type[contains(.,'Probe Response')] ]"
    elif src_type == WIRELESS_NETWORK_SRC_BEACON:
        xpath_str = "//wireless-network[ .//type[contains(.,'Beacon')] ]"
    else:
        xpath_str = "//wireless-network"
    
    networks = a_xpath_context.xpathEval(xpath_str)
    for network in networks:
        encryption_methods = get_encryption_methods_from_network_node(network)
        fill_dict_with_xpath_key(essid_encryption_methods,
                                 network, ".//essid | .//ssid",
                                 encryption_methods)
        fill_dict_with_xpath_key(bssid_encryption_methods,
                                 network, ".//BSSID",
                                 encryption_methods)
        
    a_xpath_context.xpathFreeContext()

def fill_dicts_with_xml_document_path(a_path,
                                  src_type = WIRELESS_NETWORK_SRC_ALL):
    a_document = libxml2.parseFile(a_path)
    if a_document is None:
        return False
    fill_dicts_with_xml_document(a_document, src_type)
    a_document.freeDoc()
    return True

def fill_dicts_with_xml_document_path_to_check(a_path,
                                               src_type = WIRELESS_NETWORK_SRC_ALL):
    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_dicts_with_xml_document_path(a_path, src_type)

def print_dict_access_point_as_markdown(a_dict):
    for access_point in a_dict:
        print("- "+ access_point)
        for encryption_method in a_dict[access_point]:
            print("  - " + encryption_method)

def get_sum_on_encryption_methods(dict_encryption_methods):
    a_sum = dict()
    for access_point in dict_encryption_methods:
        for encryption_method in dict_encryption_methods[access_point]:
            if encryption_method in a_sum:
                a_sum[encryption_method] += 1
            else:
                a_sum[encryption_method]  = 1
    return a_sum

def print_encryption_methods_stats_as_markdown(dict_encryption_methods):
    the_sums = get_sum_on_encryption_methods(dict_encryption_methods)
    the_sum = sum(the_sums.values())
    unknow_percentage = 100.0
    for method in the_sums:
        value = the_sums[method]
        percentage = (value * 100.0) / the_sum
        unknow_percentage -= percentage
        print("- "+ str(method) +": "+
              str(value) +" ("+ str(int(round(percentage))) +"%)")
    if unknow_percentage > 0:
        unknow_value = (unknow_percentage / 100.0) * len(dict_encryption_methods)
        print("- Unknow: "+
              str(unknow_value) + " ("+ str(int(round(unknow_percentage))) +"%)")


def print_essid_as_markdown(show_list, show_enc):
    print("# ESSID")
    if show_list:
        print_dict_access_point_as_markdown(essid_encryption_methods)
        print("")
    if show_enc:
        print("## Encryption\n")
        print_encryption_methods_stats_as_markdown(essid_encryption_methods)

def print_bssid_as_markdown(show_list, show_enc):
    print("# BSSID")
    if show_list:
        print_dict_access_point_as_markdown(bssid_encryption_methods)
        print("")
    if show_enc:
        print("## Encryption\n")
        print_encryption_methods_stats_as_markdown(bssid_encryption_methods)

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)
    
    
    show_essid      = True
    show_essid_list = True
    show_essid_enc  = True
    show_bssid      = True
    show_bssid_list = True
    show_bssid_enc  = True
    manage_options  = True
    
    wireless_network_src = WIRELESS_NETWORK_SRC_ALL
    
    
    for arg in argv[1:]:
        if manage_options and arg == "--help":
            print_help(stdout)
            exit(0)
        elif manage_options and (arg == "--hide-ssid-list" or
                               arg == "--only-encryption"):
            show_essid_list = False
            show_bssid_list = False
        elif manage_options and arg == "--no-essid":
            show_essid = False
        elif manage_options and arg == "--no-bssid":
            show_bssid = False
        elif manage_options and (arg == "--only-probe" or
                                 arg == "--probe-only"):
            wireless_network_src = WIRELESS_NETWORK_SRC_PROBE
        elif manage_options and (arg == "--only-beacon" or
                                 arg == "--beacon-only"):
            wireless_network_src = WIRELESS_NETWORK_SRC_BEACON
        elif manage_options and  arg == "--":
            manage_options = False
        else:
            fill_dicts_with_document_path_to_check(arg, wireless_network_src)
    
    
    if show_essid:
        print_essid_as_markdown(show_essid_list, show_essid_enc)
    
    if show_essid_list or show_essid_enc:
        print("")
    
    if show_bssid:
        print_bssid_as_markdown(show_bssid_list, show_bssid_enc)


if __name__ == '__main__':
    main()