VULNERABILITE D’UN SYSTEME CONNECTE PAR IP
Fonctionnement
Les modes de Windows
Le système d’exploitation Windows utilise deux modes pour maintenir l’efficacité du système d’exploitation et son intégrité: le mode d’utilisateur et le mode du noyau. L’architecture de 80386 et les processeurs plus évolués définit quatre niveaux de privilège, appelées « rings », qui protège le code du système et les données d’être modifier par inadvertance ou avec méchanceté par les codes les moins privilégiés. On appelle cela le modèle de protection d’Intel.Le niveau 0 (zéro) du privilège, aussi connu comme le mode du noyau, est le niveau le plus privilégié. Le privilège de niveau 3, ou le mode de l’utilisateur, est le plus moindre privilégié. Quand un code s’exécute à un niveau du privilège donné, on dit s’exécuter dans ce « ring ». Par exemple, le code qui s’exécute à privilège de niveau 0 est dit s’exécuter dans le « ring » 0. Le système d’exploitation Windows utilise seulement les « rings » 0 et 3. [1] Le mode du noyau (ring 0) est le mode le plus privilégié d’opération dans lequel le code a accès direct à tout le matériel et toutes les adresses de la mémoire. Les logiciels qui fonctionnent en mode noyau ont les attributs suivants: Il peut accéder directement au matériel. Il peut accéder dans toute la mémoire de l’ordinateur. Il ne peut pas être déplacé dans le fichier de page de mémoire virtuelle sur le disque dur. Il est traité à une plus haute priorité que les processus du mode utilisateur. Le cœur du code du système d’exploitation WINDOWS NT s’exécute à la mode du noyau (ring 0). Parce que les composants de mode noyau sont protégés par l’architecture du processeur, le processeur empêche un programme d’écrire sur un autre composant. Bien que le « ring » 0 fournisse ce genre de protection, ce n’est pas n’importe quoi qui peut s’exécuter dans le « ring » 0 parce que les composants ont accès au système entier. Si un composant logiciel échoue dans le « ring » 0, il peut faire arrêter le système entier. Le Windows 95 est conçu pour être le plus compatible possible des versions antérieure, plusieurs vieux mécanisme de pilotage de 16 bits et des applications ont besoin l’accès direct au matériel. WINDOWS NT ne fournit pas ce niveau d’accès, et par conséquent ce n’est pas tous les droits des applications de s’exécuter avec succès sous WINDOWS NT Workstation et WINDOWS NT Server.
Error Handling
Quand on appelle une fonction Windows, il valide les paramètres qu’on lui passe et après il essaye d’exécuter son devoir. Si on passe un paramètre invalide ou si pour quelques autres raisons l’action ne peut pas être exécutée, la valeur de retour de la fonction indique que la fonction a échoué d’une certaine façon. Le barème 1-1 indique la valeur du retour des types de donnée que la plupart des fonctions de Windows utilisent.Quand une fonction Windows détecte une erreur, il utilise un mécanisme appelé threadlocal storage ou le stockage de thread-local pour associer le nombre de code d’erreur approprié avec le thread d’appel. Cela permet aux threads de s’exécuter indépendamment de l’un de l’autre sans affecter le code d’erreur de l’un à l’autre. Quand la fonction revient à nous, sa valeur du retour indiquera qu’une erreur s’est produite. Pour voir quelle est cette erreur exactement, on appelle la fonction GetLastError. [6] Le code d’erreur est un nombre de 32 bits qui est développé dans les champs indiqué dans la table suivante. Bits: 31-30 29 28 27-16 15-0 Contents Severity Microsoft/ customer Reserved Facility code Exception code Meaning 0 = Success 1 = Informational 2 = Warning 3 = Error 0 = Microsoftdefined code 1 = customerdefined code Must be 0 Microsoftdefined Microsoft/customerdefined Tableau 1.02 : Les codes d’erreurs
Unicode
Unicode est une étape fondamentale dans le développement de toute application. Si on développe pour Microsoft Windows 2000 ou Microsoft Windows CE, on devrait développer avec Unicode. Le problème est que quelques langages et l’écriture des systèmes ont tant de symboles dans leurs ensembles de caractère qui un seul octet ou « byte » et qui offre pas plus de 256 symboles différents au maximum, ce n’est justement pas assez. Sinon les caractères de 2 octets ont mis le DBCS (double-byte character sets), qui a été créé pour supporter ces langages et l’écriture des systèmes. Quand on développe une application, on devrait sans aucun doute profiter de l’Unicode. Même si on ne projette pas utiliser notre application aujourd’hui, développer avec Unicode dans l’esprit simplifiera certainement la conversion dans le futur. De plus, Unicode : [6] Permet la facilitée d’échange des données entre langages Nous permet de distribuer un seul binaire .exe ou DLL de fichier qui supporte toutes les langages Améliore l’efficacité de notre application Les pages de code sont une notion datant au moins de MS-DOS. À cette époque-là, on n’avait que huit bits pour représenter les caractères, mais bien plus de 255 en tout. On ne pouvait donc en supporter que 255 à la fois, il fallait donc choisir. Ces choix furent regroupés dans divers « character sets » (ou charsets), et sous les environnements Microsoft, ces ensembles de caractères sont répertoriés dans les Pages de Codes numérotées. Sous un Windows moderne, ces pages de code sont toujours utilisées lors de la traduction d’une chaîne de caractères dite « ANSI » vers une chaîne de caractères Unicode. Quand Microsoft a essayé de rendre la facilité aux développeurs d’implémenter des logiciels qui fonctionnent sur les plates-formes, la différence Unicode/ANSI peut rendre des choses difficiles et typiquement l’un des plus grands problèmes. Microsoft est solidement derrière Unicode et il est fortement recommandé de l’utiliser.
Les objets du noyau ou kernel objects
Une solide compréhension des objets du noyau est essentielle pour devenir un développeur de logiciel Windows. Les objets du noyau sont utilisés par le système et par les applications que nous avons écrites pour diriger de nombreuses ressources telles que des processus, les threads, et les fichiers. Le système crée et manipule plusieurs types d’objets du noyau, tel que l’accès aux objets symboliques, l’objet événement, le fichier objets, le file-mapping objet, l’exécution des ports objets I/O, l’objet « job », l’objet mailslot, l’objet mutex, l’objet de la pipe, l’objet du processus, l’objet sémaphore, l’objet thread, et l’objet d’horloge waitable. Ces objets sont créés en appelant plusieurs fonctions. Par exemple, la fonction CreateFileMapping ordonne au système de créer un objet de file-mapping. Chaque objet du noyau n’est simplement qu’un bloc de la mémoire alloué par le 16 noyau et n’est accessible que par le noyau seulement. Ce bloc de mémoire est une structure de donnée dont les membres contiennent de l’information au sujet de l’objet. Quelques membres (le descripteur de sécurité, le compte de l’usage, etc.) ont la même caractéristique à travers tous les types d’objets, mais la plupart sont spécifiques à un type d’objet particulier. Par exemple, un objet de processus a une carte d’identité (ID) du processus, une priorité de base, et un code de sortie, alors qu’un objet de fichier a une compensation d’octet, une mode de partage, et une mode d’ouverture. [6] Remarquons que les structures de donnée d’objet du noyau sont seulement accessibles que par le noyau, alors c’est impossible pour une application de localiser ces structures de donnée dans la mémoire et directement changer leur contenu. Microsoft met en vigueur délibérément cette restriction pour assurer que les structures d’objet du noyau maintiennent leurs états logiques. Cette restriction permet aussi à Microsoft d’ajouter, d’enlever, ou de changer les membres dans ces structures sans déséquilibrer des applications. [6] Si nous ne pouvons pas changer ces structures directement, comment est-ce que nos applications manipulent ces objets du noyau? La réponse est c’est que Windows offre un ensemble de fonctions qui manipulent ces structures par des moyens bien précis. Ces objets du noyau sont toujours accessibles via ces fonctions. Quand on appelle une fonction qui crée un objet du noyau, la fonction retourne un « handle » qui identifie l’objet. On peut considérer que ce « handle » est comme une valeur obscure qui peut être utilisée par des threads dans notre processus. On passe cet « handle » aux différents types de fonctions de Windows afin que le système sache quel objet du noyau on veut manipuler. Le compte d’usage Les objets du noyau sont reconnu par le noyau, non pas par un processus. En d’autres termes, si notre processus appelle une fonction qui crée un objet du noyau et après si notre processus se termine, l’objet du noyau n’est pas détruit nécessairement. Dans la plupart des circonstances, l’objet sera détruit; mais si un autre processus utilise encore l’objet du noyau de notre processus précédent, le noyau reconnait que l’objet ne doit pas encore être détruit jusqu’à ce que l’autre processus ait cessé de l’utiliser. La chose importante qu’on doit retenir, c’est qu’un objet du noyau peut survivre au processus qui l’a créé. [6] 17 Le noyau sait combien de processus utilise l’objet particulier du noyau parce que chaque objet contient un compte d’usage. Le compte d’usage est un des membres de donnée commun à tous les types d’objets du noyau. Quand un objet est créé en premier, son compte d’usage est mis à 1. Alors quand un autre processus gagne l’accès à un objet du noyau déjà existant, le compte d’usage est incrémenté. Quand un processus se termine, le noyau décrémente automatiquement le compte d’usage pour tous les objets du noyau que leur processus sont encore ouvert. Si le compte d’usage de l’objet tombe à 0, le noyau détruit l’objet. Cela assure qu’aucun objet du noyau ne restera dans le système si aucuns processus ne le référence.
MultiThreading
Le multithreading est le fait d’exécuter des tâches simultanément (multitâche). Les termes multitâche et multithread ne sont pas totalement similaires. Le terme multithread indique une application qui effectue différentes taches simultanément. Le terme de multitâche est un terme plus général et plus commun pour parler d’un système capable de gérer plusieurs applications simultanément. Dans un programme classique, les instructions sont traitées de manière linéaire, « ligne par ligne ». En ignorant le reste du système on pourrait dire que le processeur exécute les instructions de la ligne courante, puis passe à la suivante. En réalité ce n’est pas tout à fait vrai car les autres applications doivent elles aussi avoir accès au processeur, ainsi que le système d’exploitation lui même. [6] Un thread est une unité d’exécution. Chaque thread peut avoir accès au processeur et exécuter des instructions. Un thread peut se comporter exactement comme s’il ne partageait pas le processeur (mais dans ce cas, il faudrait disposer d’autant de processeurs qu’il y a de threads). Pour éviter cet inconvénient, le système donne l’accès au processeur à chaque thread durant un temps très court (quelques millisecondes), et ceci de manière circulaire. Si le nombre de thread augmente, chaque thread devra attendre un délai plus long avant d’obtenir de nouveau l’accès au processeur. De cette manière, chaque application peut fonctionner comme si elle était seule à utiliser le processeur. La seule différence est que l’exécution est plus lente. Si le processeur est suffisamment rapide, l’exécution d’un nombre modéré de threads peut s’effectuer très rapidement. C’est le cas 18 actuellement. Dans un système utilisant Windows, le nombre de threads avoisine 50 au repos. Il dépasse rapidement 100. L’utilisation du multithread consiste à créer un programme qui comporte plusieurs threads. Ce programme peut donc exécuter plusieurs instructions « simultanément ». On peut alors se demander l’utilité d’une telle pratique. En effet, puisque le processeur est partagé, l’exécution du programme ne sera pas plus rapide. Elle sera seulement fragmentée. Un exemple simple permet de comprendre l’utilité du multithreading.
Application de MultiThreading
Considérons une application réalisant un transfert de fichier. Cette application dispose d’une interface graphique présentant une barre de progression ainsi qu’un bouton ‘annuler’. En effet l’utilisateur désire être tenu au courant de l’avancement de la copie. De plus il veut pouvoir stopper celle ci à tout moment s’il le désire. Le programme doit donc réaliser une boucle de manière à recevoir les messages. Mais il doit également s’occuper de la copie des fichiers, ce qui reste son rôle principal. Si le programme écrit sur le disque, il ne peut plus recevoir les messages. La fenêtre ne sera donc plus rafraîchie. De plus, si l’utilisateur clique sur ‘annuler’, l’application ne traitera pas cette demande puisqu’elle ne recevra même pas le message. Une telle application utilisera donc 2 threads. Le premier thread s’occupera de la réception et du traitement des messages. Le deuxième thread s’occupera de la copie des fichiers. De cette manière, la réception des messages sera effectuée même au cours de la copie. Si l’utilisateur demande l’arrêt de la copie, le premier thread devra simplement stopper l’exécution du second. La copie sera alors stoppée. Notion de processus et de thread : Le processus : Un processus est défini habituellement comme un exemple de programme courant et constitué de deux composants : [6] Un objet du noyau que le système d’exploitation utilise pour diriger le processus. L’objet du noyau c’est là aussi que le système garde l’information statistique au sujet du processus. 19 Un espace d’adresse que contient tous les exécutables ou les codes modules de DLL et les données. Il contient aussi les allocations de la mémoire dynamiques telles que le thread stacks et le heap allocations (allocations de tas). Les processus sont inertes. Pour un processus il doit avoir un thread qui s’exécute dans son contexte pour accomplir n’importe quoi; ce thread est responsable de l’exécution des codes contenus dans l’espace d’adresse du processus. En fait, un seul processus peut contenir plusieurs threads, ces derniers exécutent « simultanément » le code dans l’espace d’adresse du processus. Pour faire ceci, chaque thread a son propre ensemble (jeu, partie) de registres CPU et son propre pile. Chaque processus a au moins un thread qui exécute le code dans l’espace d’adresse du processus. S’il n’y avait pas de threads qui exécutent le code dans l’espace d’adresse du processus, il n’y aurait aucune raison pour le processus de continuer d’exister, et le système détruirait automatiquement le processus et son espace d’adresse. [6] Pour le lancement de tous ces threads, le système d’exploitation programme alloue, quelques temps du CPU pour chaque thread. Il crée l’illusion que tous les threads s’exécutent simultanément en offrant le tranche de temps (appelé quantums) aux threads. La figure ci-dessous nous montre comment cela fonctionne sur une machine avec un seul CPU. Si la machine possède de multiple CPU, l’algorithme du système d’exploitation est beaucoup plus complexe pour charger la balance des threads sur le CPU.
INTRODUCTION |