P32 Apprentissage automatique pour la détection d’attaques par déni de services
Sommaire
- 1 Description du projet
- 2 Dépôt Git
- 3 Matériel emprunté
- 4 Environnement de développement
- 5 Planning prévisionnel
- 6 État de l'art
- 7 Proposition de résolution
- 8 I - Première semaine
- 9 II - Seconde semaine
- 10 III - Troisième semaine
- 11 IV - Quatrième semaine
- 12 V - Cinquième semaine
- 13 VI - Sixième semaine
- 14 VII - Septième semaine
- 15 Conclusion
- 16 Bibliographie
Description du projet
L'objectif de ce projet est d'automatiser la détection d'attaques par déni de services sur un réseau LoRa composé d'un émetteur, d'un récepteur ainsi que d'un capteur. Les données qui transitent dans les trames sont des températures. Une attaque peut par exemple correspondre à une variation brutale du ping, au détournement de la source et autres. Pour cela, il faut analyser les trames en transit et détecter si oui ou non elles représentent une menace. Un programme, développé en Python, sera capable d'avertir les personnes concernées sur une éventuelle attaque par déni de services. Dans un premier temps, il sera nécessaire de recenser un maximum d'angles d'attaque pour être capable de prévoir ces phénomènes. Une fois ces attaques analysées, il faudra générer un DATASET de trames avec trafic normal et avec attaques. Ce dataset sera ensuite utilisé par un réseau de neurones pour qu 'il puisse l'apprendre et être capable de classer une trame normale d'une trame à risque. En parallèle à cela, un programme séquentiel viendra renforcer la détection avec une étude du ping et de renvoi automatique des trames. Pour réaliser ce projet, je dispose de deux mois à temps plein.
Dépôt Git
https://archives.plil.fr/rcavalie/PFE/tree/master
Matériel emprunté
Matériel | Quantité |
---|---|
Modules LoRa SX1276 | 3 |
STM32 | 3 |
Câbles mini-USB/USB | 3 |
Environnement de développement
Spyder (Python IDE) : https://anaconda.org/anaconda/spyder
Mbed Compiler (Modules LoRa) : https://os.mbed.com/compiler/#nav:/;
Ubuntu
Archives GitLab : https://archives.plil.fr/rcavalie/PFE
Planning prévisionnel
Tâche | Temps (jours) |
---|---|
état de l'art | 3 |
prise en main des modules LoRa / IDE Mbed | 2 |
communication / interception du trafic | 3 |
types d'attaque | 7 |
création du dataset | 7 |
architecture du réseau de neurones | 7 |
tests sur réseau | 4 |
développement d'une application facile d'usage | 3 |
État de l'art
Les attaques par déni de service consistent à rendre indisponible un service en augmentant la fréquence d'envoi de trames sur un récepteur. Il est également possible d'utiliser le rejet afin de renvoyer de manière continue une seule et même trame et tenter de saturer la BP d'un service. Un émetteur pirate pourrait par exemple usurper l'identité d'un émetteur du réseau et ainsi perturber les mesures de température.
Les DoS sont des attaques très répandues notamment dans le domaine de l'IoT ou de plus en plus d'objets connectés font leur apparition. Nous pouvons citer pour exemple la vulnérabilité de certains drones quant à ce type d'attaques.
Ici les paramètres à surveiller seront :
- L'identifiant de l'émetteur - Le ping - La puissance d'émission - Le contenu de la trame qui doit toujours indiquer une température - Le rejet de trame
Concernant le réseau LongRange, la communication se fait par modulation des ondes radios. A partir de la board SX1276, nous allons être capable d'envoyer des paquets et de les recevoir. Ainsi, il sera possible de générer un DATASET correspondant à la classe d'un trafic normal.
Proposition de résolution
I - Première semaine
Au cours de cette première semaine, j'ai pu obtenir plus de précisions sur le projet. Une fois le matériel récupéré à l'IRCICA, je suis passé par quelques exemples de code notamment l'exemple du Ping-Pong entre deux modules LoRa.
lien : https://github.com/Lora-net/LoRaMac-node
documentation sur la librairie SX1276 par Mbed (C++) : https://os.mbed.com/users/GregCr/code/SX1276Lib/docs/tip/classRadio.html#a5a96290956c521510d1bd5a7c1ac21b9
En étudiant la valeur des registres et les paramètres d'émission et de réception, je suis parvenu à créer un code capable d'émettre une trame de la forme suivante :
Un identifiant est associé à chaque émetteur et à chaque récepteur pour renforcer une certaine sécurité lors de la réception. Je souhaiterais ajouter un bit de parité dans la trame afin de vérifier si le message a été correctement reçu ou non.
Une fois les paramètres définis et la radio configurée, j'ai été capable d'émettre mes trames toutes les 10 secondes (en émission continue) à partir de la fonction Send :
Pour la partie réception, les paramètres restent identiques. Je vérifie le contenu du buffer chaque seconde. Si celui-ci est plein alors je compare le contenu de son entête à savoir les 11 premières cases du tableau pour savoir si le récepteur souhaite lire les trames de l'émetteur (sécurité).
La lecture radio en continue se fait par la fonction Rx :
Si tel est le cas, alors la trame est lue toutes les 1 seconde.
Il y a donc un fichier emetteur.cpp et recepteur.cpp pour la réception et l'émission. Chaque fichier est compilé avec la librairie SX1276 via le Mbed Compiler. Un fichier binaire permet ensuite de flasher le microcontrôleur.
Pour la température, je vais générer des valeurs aléatoires afin de simuler au mieux le comportement d'un capteur utilisé en industrie. L'objectif va être d’enregistrer ces trames dans un fichier .csv ou .txt pour les utiliser lors d'un apprentissage profond.
Le code est assez générique et vous permettra d'adapter les trames selon votre application. Il sera juste nécessaire de modifier les constantes en ce qui concerne la taille du Buffer : BUFFER_SIZE.
II - Seconde semaine
Au cours de cette semaine, j'ai travaillé sur la création des datasets lors des séances mais également sur l'interface graphique python permettant d'afficher les données utiles à la surveillance du trafic.
J'ai donc créé un fichier python appelé recorder.py qui permet d'enregistrer :
- les trames normales - le ping en trafic normal - le ping en attaque - le rssi en temps normal - le rssi en attaque
Pour cela, j'écoute en boucle le port série de COM4 de ma machine Windows et enregistre 3000 individus pour chaque dataset. La trame correspond à la première ligne lue avec le serial python. La puissance de réception de la trame arrive en seconde ligne.
Le dataset des trames est un simple enregistrement de la première ligne reçue. La trame est écrite dans un fichier trame_norm.csv. Les trames en elles-mêmes ne permettent pas de réaliser une classification entre un trafic normal et une attaque DoS. Je me suis donc plus penché sur le ping et sur le rssi.
Chaque élément est enregistré avec son numéro de classe : 0 pour normal, 1 pour attaque. L'utilité de cette démarche sera expliquée lors de la phase d'apprentissage du réseau de neurones.
PING
Pour procéder à l'enregistrement du ping normal, j'ai travaillé avec un envoi toutes les 1000 ms et une réception toutes les 300 ms. Jusque là, rien d'anormal. En descendant petit à petit la temporisation dans ma fonction d'envoi, j'ai noté des mauvaises réceptions de trames et une perturbation importante du trafic en dessous de 100 ms. J'ai donc réalisé toutes mes mesures de ping_attaque.csv avec un envoi de 50 ms et une réception de 300 ms.
Pour mesurer le ping j'ai utilisé la librairie time et ai fait la soustraction entre le temps d'arrivé d'un première trame et de la seconde.
L'enregistrement se fait de la même manière que pour les trames.
Voici la représentation de la population du dataset après enregistrement.
Il est possible de distinguer clairement deux classe. La classe 0 avec un ping normal et la classe 1 avec un ping fortement élevé par rapport au trafic que nous jugeons "normal" pour notre application. Cette représentation est très importante car elle permet de comprendre comment le réseau fera la distinction entre nos deux classes (normale et d'attaque). Il est réellement important de fournir un dataset de qualité avec des valeurs qui soient les plus uniformes possible dans leurs classes respectives.
RSSI
Pour le RSSI, j'ai opté pour une mesure du delta entre deux trames. C'est à dire que si la puissance du signal reçu varie trop fortement, alors une attaque est peut-être en cours. Comment est-ce possible ? Si un émetteur est réglé sur une puissance d'envoi de 14 dBm, un autre émetteur paramétré sur un envoi de 20 dBm pourrait l'usurper ou perturber le trafic. Lors de mes essais réalisés avec deux émetteurs (14 et 20 dBm d'envoi), j'ai noté une attaque par DoS visible lorsque l'émetteur le plus puissant est proche du récepteur.
Ainsi, il est nécessaire de déterminé cet écart entre deux trames reçues et de notifier l'utilisateur du programme.
Pour mesurer le RSSI reçu à chaque trame, je travaille avec mon programme recepteur.cpp pour capter et renvoyer sur le port série la puissance.
La fonction Rx inscrit dans la variable RssiValue la valeur de la puissance.
Ensuite, le calcul est fait du côté python. Je convertis la puissance reçue d'ASCII à décimale et calcule la valeur absolue entre la puissance de la trame suivante. Ensuite, je procède à l'enregistrement.
Pour les essais en trafic normal, j'ai simplement réalisé des acquisitions entre un émetteur et un récepteur avec un envoi à 14 dBm. Pour générer des attaques, j'ai ajouté un émetteur de 20 dBm qui émettait par alternances pour perturber le trafic. J'ai ensuite enregistré que les meilleurs individus des delta_rssi en tant que dataset rssi_attack.csv
Voici le dataset obtenu après un tri des données :
Voici une photo du montage m'ayant permis de générer mes datasets :
Réseau de neurones ANN
Pour créer mon réseau de neurones, j'ai utilisé les librairies suivantes :
- Keras - Tensorflow
Tensorflow est une librairie d'IA développée par Google. Keras est une implémentation de cette librairie. Elle permet de réaliser l'architecture et les calculs en sortie du réseau de neurones.
Pour débuter, il faut importer les datasets enregistrés précédemment. Je rappelle que nous allons travailler avec le delta_rssi et le ping. 4 fichiers sont donc concernés :
- ping_norm.csv - ping_attack.csv - rssi_norm.csv - rssi_attack.csv
Le but est de mettre ces fichiers sous une matrice dataset de la forme suivante :
Je tiens à préciser qu'il n'y a aucune conséquence dans l'inversion des colonnes PING et RSSI. Le réseau de neurones sera donc composé de deux entrées. Nous allons donc lui passer à chaque itération un scalaire de taille 2 contenant PING et RSSI. ainsi que la sortie associée (attaque ou normale). En entrée du réseau de neurones, toutes les valeurs sont normalisées entre 0 et 1 ou -1 et 1 pour réaliser un traitement avec les fonctions d'activations (linéaire - sigmoïdale - sigmoïdale symétrique ...).
Ensuite, j'ai travaillé sur l'architecture du réseau de neurones. J'ai réalisé une tentative à zéro, une et deux couches cachées. Le fait de ne mettre aucune couche cachée parvient parfaitement à approximer notre fonction d'erreur pour cette problématique.
Le réseau de neurones final ressemble donc à cela :
A partir de la méthode fit je peux entrainer le réseau de neurones. Les paramètres retenus et suffisants (après différents essais) sont :
- epochs = 50 : nombre d'itération ou nombre de fois où l'on parcourt le dataset - batch_size = 1 : on traite le dataset ligne par ligne. si batch_size = 5, moyenne des 5 lignes
Et le learning se déploie ! Je tiens à préciser que plusieurs paramétrages et architectures ont été testées car il est difficile de calculer au préalable l'efficacité d'un réseau. Souvent, ce sont des choses qui se constatent de manière empirique. Il est très compliqué de vérifier ce qui se passe dans la boite noire une fois les données d'apprentissage entrées.
III - Troisième semaine
Au cours de cette troisième semaine, j'ai travaillé sur la récupération des données via port série et sur l'entrée de ces données afin de réaliser des prédictions via mon réseau de neurones. J'ai rapidement réussi à enregistrer les données comme voulu. Cependant, j'ai perdu un peu de temps sur leur format. Les valeurs ont été castées en int64 comme elles l'ont été lors de l'apprentissage. J'avais aussi eu quelques problèmes par rapport au format de ma matrice d'entrée qui devait être de la forme [1][2]. Avec numpy, qui est l'utilitaire python permettant de gérer les matrices et listes, je suis finalement parvenu à envoyer mes données en entrée du réseau en réalisant une transposée.
Ainsi le programme main se compose :
- d'une fonction initialisant l'écoute du port série - d'une fonction créant un réseau ANN loadant le learning du réseau précédemment effectué - d'une fonction d'affichage en boucle faisant appel à la fonction de prédiction avec pour arguments le rssi et le ping en temps réel
J'ai retenu quelques problèmes de carte graphique avec un erreur de Keras / Tensorflow.
Dans cet exemple, nous pouvons voir que une hausse de puissance de réception est détectée comme une ATTAQUE. la prédiction x est donc supérieure à 0,5. Dans l'autre cas, nous sommes à 27 dBm, une puissance que le réseau connait comme "habituelle". Il pronostique donc un trafic où rien n'est à signaler. x est donc inférieur à 0,5. Prochainement, des tests avec ping, avec rssi seulement et en combinaison seront réalisés. Je chercherai ensuite à améliorer le sécurité et la précision des détections d'attaque.
Le programme main m'a pris du temps à coder car il contient de nombreuses variables globales et temporaires à actualiser à chaque tour de boucle. La mesure du rssi et du ping se fait sur deux trames. J'ai donc essayé plusieurs structures car j'obtenais parfois un code lent à exécuter et donc un affichage en conséquence.
Tests | Catégorie |
---|---|
1000 ms - 14 dBm | Normal |
50 ms - 20 dBm | Attaque |
Pour le moment, les valeurs de puissance semble parfois élevées par rapport à la puissance du signal envoyé. Cela amène parfois à de fausses prédictions sur tout le trafic.
Je fais également face à des problèmes de décalages temporels entre l'envoi, la réception et l'affichage.
Après une séance de révision de projet, je suis forcé de devoir diminuer ma fréquence d'envoi de trames car mes tests sont faits sur des laps de temps trop rapides pour exécuter le code de traitement entre chaque envoi. Je vais donc en semaine prochaine :
- Ajouter un caractère de début de trame - Un caractère de fin de trame - Un compteur de trame pour voir si tout est bien reçu - Les attaques seront manifestées par un envoi toutes les une secondes - Le trafic normal sera caractérisé par un envoi toutes les cinq secondes
IV - Quatrième semaine
Remise en forme de la trame
La trame est désormais de la forme suivante :
[0] : | : caractère de début de trame
[0...5] : entête correspondant à l'ID de l'émetteur
[6...13] : entête correspondant à l'ID du récepteur
[14] : | : caractère de fin de trame
[15...17] : compteur de trame allant de 0 à 100
J'ai donc modifié les constantes de taille pour passer de 13 à 18 octets. De plus, j'ai fait appel à ma fonction convertissant les entiers en char ASCII.
Ensuite dans l'algorithme, je coderai une fonction permettant de savoir si des trames n'ont pas été perdues.
Les trames une fois mises en forme, je relance les acquisitions du dataset. Cela prend du temps car je réalise 3000 acquisitions dont une toutes les 5 secondes pour le trafic normal et une toutes les 1 seconde pour les attaques. Il faudra également voir si la puissance ne doit pas être recalculée pour ce nouveau modèle.
Acquisitions
Durant 2 jours et demi, j'ai réalisé toutes les nouvelles acquisitions post modifications. Le dataset semble désormais plus hétérogène avec un ping plus lent. La puissance, elle, reste dans le même ordre de grandeur. Cependant je fais face à une erreur de type de données lors de l'apprentissage.
Une fois ce problème réglé, l'algorithme sera réellement capable de réaliser des prédictions en temps réel. Il n'y aura plus de problème de décalage puisque les durées de temps entre chaque calcul seront plus largement espacées et nous serons de plus en mesure d'observer avec plus de précision le comportement du code.
Le problème est réglé. Il s'agissait d'une colonne qui contenait un NaN dans ma base de données d'entrainement. J'ai supprimé cette ligne manuellement dans mon fichier csv.
Dataset du nouveau trafic normal | ping : 5000 ms | puissance d'envoi : 14dBm
Dataset du nouveau trafic attaque | ping : 1000 ms | puissance d'envoi : 20dBm
Tests sur le nouvel algorithme
Au niveau des prédictions, la précision augmente. Je suis désormais capable de prédire jusqu'à 70% de bonnes réponses en temps réel. Cependant, lorsque les émetteurs sont débranchés, mon récepteur capte toujours un envoi de trames. Buffer rempli, envoi retardé ? Après implémentation du réseau de neurones, je souhaiterais réaliser des prédictions à partir de la méthode des K-plus proches voisins. Je pense que cette méthode est plus adaptée au problème même si le réseau de neurones a fait ses preuves.
Méthode des K-plus proches voisins
La méthode des K-plus proches voisins consiste à prendre un point à classer et à calculer les distances euclidiennes le séparant de ses K plus proches voisins. Si le nombre de voisins appartenant à une classe 0 est supérieur au nombre de voisins appartenant à la classe 1, alors le point sera classé en 0. Nous pouvons ainsi réaliser des prédictions sur la puissance reçue mais aussi sur le ping.
L'objectif est de mettre la matrice de données sous cette forme :
Ensuite, l'objectif est de compter combien, parmi les k premières lignes de ma matrice, appartiennent à une classe. Ensuite je peux pronostiquer le résultat selon le ping ou la puissance reçue.
d = sqrt((ping - ping_dataset)^2)
V - Cinquième semaine
La méthode des KPPV a été codée ce week-end et testée ce lundi. Elle fonctionne très bien et nécessite moins de puissance de calcul. Je pense que l'intelligence artificielle probabiliste supervisée est donc une meilleure solution à la problématique.
Ici très clairement, une augmentation de la puissance et une diminution du ping traduisent une attaque de la part de notre émetteur pirate. J'ai paramétré mon code avec k = 5 voisins. Une trop grande valeur de k risquerait de fausser tous mes résultats de prédiction.
Dans ce sens la méthode des KPPV est celle que je souhaiterais développer pour cette IA. Je dois maintenant coder une fonction capable de traiter le rejet. Pour cela, je vais utiliser mon compteur de trames et vérifier si une trame avec le même contenu est reçue plusieurs fois d'affilée.
Au niveau du ping, pratiquement aucune erreur de classification n'est observée. En revanche, avec un dataset où les deux classes se chevauchent, comme c'est le cas pour la puissance RSSI, j'observe des erreurs sur certaines trames.
Ce mardi, nous avons fait une nouvelle mise au point sur l'avancée du projet. Je poursuis les démarches de repeuplement de la base de données à chaque mesure de ping et de puissance. Je vais également poursuivre la création d'une application web affichant les résultats en "temps réel" pour contrôler le trafic.
L'application est faite sur des bases de javascript et de html. Elle vient chercher les données dans des fichiers csv pour ensuite les afficher sur des graphiques.
Pour réaliser cette base d'application web, je me suis servis de CanvasJS : http://canvasjs.com Les fichiers de données utilisés par l'application sont volontairement remis à 0 à chaque lancement du programme python.
Voici a quoi ressemblera l'application finale :
J'applique un refresh chaque seconde. Les graphiques et les indicateurs d'attaque évoluent. Les indicateurs changent de couleur selon le trafic : passage du vert au rouge lors d'une attaque.
Tests sur trafic
Pour réaliser ces tests, j'ai laissé tourner mon algorithme durant une demi-heure avec un trafic normal et suis venu ajouter un émetteur pirate entre l'émetteur et le récepteur. Nous observons les graphes suivants :
Nous constatons que les attaques sont donc détectées. Les prédictions au niveau du ping sont vraiment très précises (99%). Au niveau du RSSI, un peu moins avec environ 70 à 80% de détections d'anomalies correctes.
VI - Sixième semaine
Tests
Pour plus de réussite dans les prédictions concernant le RSSI, j'ai choisi de réaliser des tests en faisant varier la valeur de k pour la méthode des KPPV.
Je réalise des nouvelles acquisitions sur le rssi car celui-ci est sujet à de fortes fluctuations selon l'environnement. J'espère obtenir un dataset plus hétérogène après enregistrement. L'objectif est de stabiliser les prédictions de puissance sur mon application web.
J'ai également ajouté une ligne permettant de peupler le dataset pour renforcer les connaissances dans le cas des K-plus proches voisins. Pour réaliser un apprentissage renforcé par réseau de neurones, il m'aurait fallu utiliser une autre bibliothèque que Keras.
Dans un environnement plus stable, j'ai réalisé des tests de puissance en fonction de la distance séparant émetteur et récepteur.
Voici les résultats obtenus :
Comparaison
Nous constatons que la puissance reçue varie fortement selon la distance. Le système doit donc être très stable si l'on souhaite réaliser des prédictions précises quant aux attaques. Un solution serait d'étudier la puissance sur une plus longue plage de temps et de tirer des conclusions à fréquence réduite et non plus chaque seconde comme notre application le fait.
Concernant la variation du K, nous retrouvons de très bons résultats concernant le ping car le dataset est très hétérogène :
Ainsi, la valeur de K importe peu sur le taux de succès aux prédictions.
Lorsque nous regardons de plus près le dataset du RSSI, nous constatons que la frontière entre un trafic normal et les attaques est plus floue au niveau des premiers échantillons :
Cela explique donc une plus forte variation du taux de classification en fonction de l'évolution de K. Si K est trop élevé, il risque de couvrir des valeurs de la classe concurrente et donc d'induire une plus grande erreur.
VII - Septième semaine
- Rapport : Fichier:Rapport PFE Cavalieri.pdf
- Présentation : https://prezi.com/view/tBDSxTGAe4KZ21YKhxgY/
- Code : https://archives.plil.fr/rcavalie/PFE/tree/master
Conclusion
Ce projet m'a permis d'utiliser mes connaissances en Machine Learning dans un contexte d'étude de réseau. Il m'a fallu établir une stratégie afin de mettre en place un système pouvant fournir un dataset représentatif :
- d'attaques
- normal
Pour cela, je disposais de deux émetteurs LoRa et d'un récepteur. Un premier émetteur se chargeait d'envoyer des données selon un trafic normal en ping et en puissance. Le second lui émettait avec un RSSI plus puissant et un ping plus rapide. Les programmes ayant permis de flasher les STM32 ont été écrits en C++ à partir de bibliothèques fournies par la plateforme Mbed. Ensuite, à partir de la lecture du port série de chaque microcontrôleur, je suis parvenu à enregistrer les données utiles à la classification de l'état du trafic :
- RSSI
- PING
- TRAMES BRUTES
Une fois enregistrées, ces données sont exportées au format csv pour être réutilisées soit dans l'apprentissage du réseau de neurones soit dans la classification par méthode des k-plus proches voisins. Une application web écrite en Javascript vient faire office d'interface homme-machine afin d'avertir les utilisateurs via des graphiques et des avertisseurs de l'état du trafic. La méthode des k-plus proches voisins s'est avérée être plus efficace dans la classification avec un taux de réussite de l'ordre de 95% contre 80% pour le réseau de neurones qui de plus demandait une implémentation et un traitement préliminaire des données plus important.
Ce projet m'a permis d'apprendre et d'améliorer mes connaissances en :
- Python
- Javascript
- Machine learning
- Gestion de projet
- LoRa
- C++
Toutes les tâches du planning prévisionnel ont été achevées.
Bibliographie
https://www.udemy.com/deeplearning/learn/v4/content
http://www.journaldunet.com/ebusiness/internet-mobile/1197635-lora-reseau-differences-sigfox/
https://os.mbed.com/users/GregCr/code/SX1276Lib/docs/tip/classRadio.html
http://www.st.com/en/microcontrollers/stm32-32-bit-arm-cortex-mcus.html
https://www.apachefriends.org/fr/index.html
https://www.zingchart.com/docs/tutorials/loading-data/parsing-csv-files/