• Aucun résultat trouvé

4.4 Analyse des déviances

5.1.3 Rejeu des automates

Notre but étant de se passer de l’installation et de la configuration des applications vulnérables durant le processus d’audit, on cherche à rejouer les attaques et usages lé-

5.1. Trafic d’évaluation 77

Figure 5.2 – Génération d’un automate

gitimes déjà exécutés dans le processus de génération des automates. Notre objectif est que les outils émettant des requêtes vers les applications aient le même comportement lors de l’utilisation d’automates à la place des applications. Pour ce faire, nous avons conçu et mis en œuvre un autre outil en Python (appelé automata player) dédié au rejeu des automates. Cet outil léger est utilisé dans nos machines virtuelles importées dans l’infrastructure clone. Son travail consiste à écouter le trafic entrant afin d’en- voyer les réponses appropriées aux requêtes reçues, en fonction des automates dont il dispose.

A son lancement, l’automata player lit les fichiers pickle contenant les automates associés à l’application dont on veut simuler des comportements réseau. Il les regroupe, en fusionnant les états équivalents (au minimum, l’état initial devient commun). En- suite, il lance deux threads :

– Le premier crée un socket TCP pour écouter les connexions entrantes, et lance un nouveau thread à chaque fois qu’une connexion TCP est initiée. Ces threads écoutent les paquets TCP entrant au sein de leur connexion.

– Le second crée un socket UDP et écoute les paquets UDP entrants.

Le principe de sélection des réponses à envoyer, appliqué lors de la réception d’une requête par appel de la fonction select_responses() (l. 36-51 ), est illustré par l’Algo- rithme 3. Une requête ou une réponse est composée d’un ou plusieurs paquets réseau contenant des payloads (données utiles relatives à l’application) et transmis dans la même direction. L’état courant de l’automate est initialisé à son état initial, puis mis à jour durant l’exécution de l’algorithme. Ce dernier consiste, pour une payload reçue, à trouver la payload la plus ressemblante dans l’automate depuis à la fois l’état courant et l’état initial (fonction find_payload(), l. 4-23 ), et à trouver la payload suivante à envoyer en tant que réponse (fonction find_payload_to_send(), l. 24-35 ).

Algorithme 3 Sélection des réponses

Require: initial_state : pointeur sur l’état initial de l’automate.

Require: current_state : pointeur sur l’était courant de l’automate, initialisé à l’état initial. 1: function payload_match(state, prev_match_score, payload1, payload2)

2: return (levenshtein(payload1, payload2) > prev_match_score ? levenshtein(payload1, payload2) : F alse) 3: end function

4: function find_payload(state, payload, previous_match_score, proto) 5: score ← previous_match_score

6: for s ∈ state[”children”] do

7: if s[”received”] and s[”proto”] == proto then 8: concat_payloads ← s[”payload”]

9: last_state ← s

10: if s[”proto”] == ”T CP ” then

11: (last_state, concat_payloads) ← concat_tcp_payloads(s, concat_payloads) 12: end if

13: match_score ← payload_match(score, concat_payloads, payload) 14: if match_score then

15: score ← match_score 16: current_state ← last_state 17: end if

18: else

19: score ← f ind_payload(s, payload, score, proto) 20: end if

21: end for 22: return score 23: end function

24: function find_payload_to_send(state, prev_selected_payload, proto) 25: p_to_send ← prev_selected_payload

26: for s ∈ state[”children”] do

27: if s[”proto”] == proto and !s[”received”] then 28: payload_to_send ← s[”payload”]

29: current_state ← s

30: else if s[”proto”] �= proto then

31: p_to_send ← f ind_payload_to_send(s, p_to_send, proto) 32: end if

33: end for

34: return p_to_send 35: end function

36: function select_responses(received_payload, proto) 37: score ← 0 ; responses ← ∅ ; next_payload ← ””

38: score ← f ind_payload(current_state, received_payload, score, proto) 39: score ← f ind_payload(initial_state, received_payload, score, proto) 40: if !automaton_finished(current_state) then

41: next_p ← f ind_payload_to_send(current_state, next_p, proto) 42: while next_p do

43: responses ← responses ∪ next_p 44: next_p = ””

45: if !automaton_finished(current_state) then

46: next_p ← f ind_payload_to_send(current_state, next_p, proto) 47: end if

48: end while 49: end if

50: return responses 51: end function

5.1. Trafic d’évaluation 79 Il est à noter que la recherche de payload reçue est effectuée pour les états fils de l’état courant afin de pouvoir suivre l’évolution des échanges de paquets, mais aussi pour les états fils de l’état initial afin de pouvoir répondre à un éventuel nouvel usage de l’application. Ainsi, on n’a pas besoin de recharger les automates des applications à chaque usage différent (légitime ou malveillant) d’une même application.

Une payload reçue est délivrée par un socket, et est issue d’un paquet UDP, ou bien d’un ou plusieurs paquets TCP. En effet, dans le cas de TCP, la payload peut être répartie sur plusieurs paquets, car contrairement à un protocole sans connexion orienté message comme UDP, TCP est un protocole avec connexion orienté flux d’octets qui ne respecte pas la frontière initiale des messages. Ainsi, nous avons dissocié deux traitements différents pour chercher la réponse sur réception d’une payload :

– Dans le cas d’UDP, la payload reçue est comparée (à l’aide d’une fonction de comparaison de chaînes de caractères) avec les payloads des fils de l’état courant et des fils de l’état initial. Puis, une fois qu’on a trouvé la payload la plus sem- blable, chacune des payloads des états suivant celui contenant la payload la plus semblable et dont la direction reste opposée (paquet envoyé par l’application) est sélectionnée et envoyée en tant que réponse dans un paquet UDP.

– Dans le cas de TCP, tant que la direction associée est la même (paquet reçu par l’application), les payloads contiguës des états suivant l’état courant et celles suivant l’état initial sont concaténées, puis ces concaténations sont comparées à la payload reçue. Ensuite, une fois qu’on a trouvé la concaténation de payloads la plus semblable, à partir de l’état contenant la dernière payload concaténée, les payloads des états suivant sont aussi concaténées tant que la direction est la même (paquet envoyé par l’application), puis envoyées en tant que réponse dans un flux TCP.

Enfin, une fois que la réponse TCP ou UDP est envoyée, l’état courant de l’automate est mis à jour, et il est vérifié si l’automate a atteint son état final. Il est à noter que pour effectuer la comparaison de payloads, nous avons utilisé une implémentation de la distance de Levenshtein [22].

En plus d’être portable et exécutable en ligne de commande, l’automata player développé répond à nos besoins. Un algorithme de calcul de distance mathématique de chaînes de caractères (comme la distance de Levenshtein) est suffisant pour com- parer et identifier les payloads même si certains éléments des payloads reçues diffèrent des payloads à trouver dans l’automate (i.e., la similarité n’est pas parfaite). En ce qui concerne les protocoles binaires, un algorithme de calcul de distance de chaînes de caractères appliqué aux données binaires donne de bonnes performances à la vue de nos expérimentations. De plus, la reconnaissance d’une payload est assez rapide et très peu propice à une erreur étant donné le nombre d’états limité des automates et étant donné que nous réduisons les comparaisons depuis à la fois l’état courant et l’état initial. Aussi, nos études sur les codes sources d’exploits et les traces réseau envoyées nous ont montré que les programmes d’exploit ne font pas une analyse pous- sée du contenu des réponses reçues. C’est pourquoi nous n’effectuons pas une analyse et modification des payloads envoyées en tant que réponse en fonction des contextes.

Concernant les systèmes de détection d’intrusion réseau, les signatures sont générale- ment déclenchées par les requêtes. L’essentiel est donc d’assurer l’envoi de réponses pour permettre l’émission d’autant de requêtes malveillantes que si l’application était vraiment déployée pour assurer les réponses. Dans le cas où les réponses lèveraient les alertes, nous avons constaté que les paramètres variables (timestamps, adresses IP, noms d’hôtes, numéro de versions, etc.) n’affectent pas la manière de déclencher les alertes. Ceci est cohérent, puisque les signatures ne peuvent pas dépendre de contextes si spécifiques, auquel cas elles seraient très difficilement déclenchées.

Documents relatifs