• Aucun résultat trouvé

Techniques utilisées par les rootkits

N/A
N/A
Protected

Academic year: 2022

Partager "Techniques utilisées par les rootkits"

Copied!
79
0
0

Texte intégral

(1)

Techniques utilisées par les rootkits

Etudiant :

Florent Wenger florent_wenger@hotmail.com

Professeur : Gérald Litzistorf

Laboratoire de transmission de données, novembre 2006

En collaboration avec la société

(2)

TABLE DES MATIERES

1. PREFACE 1

1.1. ENONCE 1

1.1.1. DESCRIPTIF 1

1.1.2. TRAVAIL DEMANDE 1

2. FAMILIARISATION AVEC WINDOWS 2

2.1. PREAMBULE 2

2.1.1. INTRODUCTION 2

2.1.2. COMPLEXITE VS SECURITE 2

2.1.3. CADRE DE L'ETUDE 3

2.2. PREMIERE APPROCHE 3

2.2.1. SYSTEME D'EXPLOITATION 3

2.2.2. L'APIWINDOWS 5

2.2.3. CONCEPTS FONDAMENTAUX 5

2.3. FORMAT PE, OCCUPATION MEMOIRE ET DLLS 7

2.3.1. FORMAT PE 7

2.3.2. IMPORT ADDRESS TABLE 12

2.3.3. OCCUPATION MEMOIRE : THEORIE 16

2.3.4. OCCUPATION MEMOIRE : PRATIQUE 19

2.3.5. DEPENDANCES 23

2.4. PROCESSUS, THREADS ET HANDLES 25

2.4.1. PROCESSUS 25

2.4.2. THREADS 28

2.4.3. HANDLES 30

2.4.4. FICHIERS MAPPES ET MEMOIRE PARTAGEE 32

2.5. MODELE DE SECURITE 33

2.5.1. UTILISATEUR ET JETON D'ACCES 33

2.5.2. DROITS, HANDLES ET ACLS 35

2.5.3. PRIVILEGES 36

2.5.4. AUTRES MECANISMES :SRP,DEP 39

2.5.5. SOURCES 42

2.6. CONCLUSION 42

3. TECHNIQUES UTILISEES PAR LES ROOTKITS 43

3.1. QU'EST-CE QU'UN ROOTKIT? 43

3.1.1. DEFINITION 43

3.1.2. TYPES DE ROOTKITS 43

3.1.3. LE ROOTKIT NTILLUSION 44

(3)

3.2.1. MOTIVATION 44

3.2.2. EVOLUTION DE LA TECHNIQUE 45

3.2.3. METHODE STATIQUE OU DYNAMIQUE? 45

3.2.4. PIRATAGE DE L'API 46

3.3. TECHNIQUES FONDAMENTALES 47

3.3.1. LOCALISATION DES PROCESSUS 47

3.3.2. VUE D'ENSEMBLE DES HOOKS 48

3.4. TECHNIQUES D'INJECTION 50

3.4.1. PROBLEMATIQUE DU MAPPING 50

3.4.2. INJECTION DE DLL VIA LA BASE DE REGISTRE 52 3.4.3. INJECTION DE DLL GRACE AUX HOOKS 53 3.4.4. INJECTION DE DLL AVEC CREATEREMOTETHREAD &LOADLIBRARY 53 3.4.5. INJECTION DE CODE AVEC WRITEPROCESSMEMORY 56

3.5. TECHNIQUES D'INTERCEPTION 56

3.5.1. A QUEL NIVEAU? 56

3.5.2. MODIFICATION DE L'IAT 58

3.5.3. INLINE PATCHING 61

3.5.4. EXPERIENCE AVEC DETOURS ET WINDBG 63

3.5.5. REDIRECTION DE DLL 67

3.6. TECHNIQUES ADDITIONNELLES 68

3.6.1. MODULE FANTOME 68

3.6.2. CONTRE LE REVERSING 69

3.6.3. MESURES ET CONTRE-MESURES 70

3.7. ET SOUS WINDOWS VISTA? 70

3.7.1. 1ER TEST : TERMINAISON DES PROCESSUS 71 3.7.2. 2EME TEST : MODIFICATION DE L'IAT 71 3.7.3. 3EME TEST : INLINE PATCHING AVEC DETOURS 71 3.7.4. 4EME TEST : INTERCEPTION DE MOTS DE PASSE 72

3.7.5. BILAN 74

4. POSTFACE 75

4.1. CONCLUSION 75

4.2. REMERCIEMENTS 76

4.3. REFERENCES 76

(4)

1. Préface

1.1. Enoncé 1.1.1. Descriptif

La compréhension des techniques d'attaque sur Windows peut être un moyen de mieux lutter contre certains malwares.

Dans un but didactique et en complément au labo Windows Forensics proposé par le laboratoire, l'étudiant va analyser diverses techniques (hook, injection dll, …) utilisées par les rootkits en mode user.

Il doit identifier les outils (debugger, …) à utiliser afin de construire des scénarios pédagogiques basés sur des exemples (code machine) précis complètement documentés.

Il comparera les résultats obtenus sous Windows XP SP2 et sous Windows Vista.

1.1.2. Travail demandé

Ce travail se compose des parties suivantes :

1) Familiarisation 5 semaines

Etudier l'architecture logicielle (module, thread, handle, dll, …) ainsi que l'occupation mémoire.

Etudier les mécanismes de chargement (loader, format PE, …) .

Etudier les mécanismes de protection mémoire (private & not shareable page, shared memory & mapped files, data execution prevention) ainsi que le modèle de sécurité (token, privileges).

2) Techniques utilisées par les rootkits 5 semaines Etudier diverses techniques (IAT patching, inline patchning, dll injection, code injection, …).

Choisir des exemples présentant des risques importants pour l'utilisateur.

Mettre en œuvre.

3) A choix (hors mémoire) 2 semaines

Contournement d'un anti-virus / d'un firewall personnel / de Windows File Protection – Sécurité avec .NET 3.0 / …

Sous réserve de modifications en cours de travail.

Liens

http://www.rootkit.com http://www.uninformed.org/

http://www.phrack.org/62/p62-0x0c_Win32_Portable_Userland_Rootkit.txt Gérald LITZISTORF, sept. 06 Travail en collaboration avec Ellisys (société créée par des diplômés du laboratoire).

(5)

2. Familiarisation avec Windows

2.1. Préambule

2.1.1. Introduction

La 1ère partie de ce travail a pour but de se familiariser avec les concepts et les mécanismes fondamentaux du fonctionnement interne de Windows. Rédigée dans une perspective didactique, elle devrait permettre à un étudiant en fin d'études HES en filière informatique de s'initier aux fondements de ce système d'exploitation.

Le point de départ incontournable à ce sujet est le livre Windows Internals, écrit par Mark Russinovitch, en parallèle avec la documentation officielle MSDN. Dans tous les cas, les références données indiqueront nos documents source pour vérification ou approfondissement.

Nous accompagneront notre propos par des expériences avec divers utilitaires existants, afin de montrer ce qui se passe vraiment dans le cœur de Windows. Nous inclurons également des fragments de code C++ ou assembleur qui pourront servir de base à un développement ultérieur à l'aide, par exemple, de Microsoft Visual Studio 2005.

2.1.2. Complexité vs Sécurité

Tout d'abord, essayons d'avoir une vue globale de la situation. Windows est un système d'exploitation (operating system, OS), donc une suite logicielle chargée de gérer les ressources matérielles et logicielles d'un PC. La complexité de Windows vient du fait qu'il est :

Caractéristique Signification Multitâche Plusieurs processus peuvent s'exécuter

concurremment sur un même processeur Multiprocesseur Windows XP Pro prend en charge 1 ou 2

processeurs, (d'autres versions jusqu'à 64) Multiutilisateur Plusieurs sessions utilisateur peuvent être

ouvertes simultanément

De plus, Windows XP descend de la famille NT. Il doit, dans une certaine mesure, assurer la compatibilité avec les versions antérieures. Ajoutons à cela la multiplicité d'architectures matérielles (32/64 bits) et d'environnements sous-système (Windows, POSIX).

Il y a encore les problématiques de l'internationalisation qui introduit des différences régionales et linguistiques, de la mise à jour qui permet un nombre théoriquement infini de combinaisons de versions installées des composants (DLLs, EXEs, etc.) et de la configuration qui est plus que déterminante pour la sécurité.

(6)

La complexité est l'ennemie de la sécurité : un tel OS est un casse-tête non seulement à comprendre, mais aussi à sécuriser. En effet, un logiciel malveillant (malware) peut exploiter la plus petite faille qui se trouve peut-être dans un recoin méconnu du système.

Heureusement pour nous, les hackers cherchent généralement des techniques d'attaque fonctionnant sur le plus grand nombre de machines semblables. Les risques les plus probables touchent donc les composants les plus répandus. Par conséquent, il vaut la peine d'étudier les concepts fondamentaux de Windows.

2.1.3. Cadre de l'étude

Ayant aperçu combien le domaine est à la fois vaste et complexe, il est nécessaire de bien délimiter le cadre de notre étude :

MS Windows XP Professional Service Pack 2, version anglaise Architecture 32 bits x86, monoprocesseur

L'ordinateur n'appartient pas à un domaine

La plupart des observations qui suivent s'appliquent également à d'autres plates-formes, c'est-à-dire à d'autres architectures (p. ex. 64 bits ou multiprocesseur) et d'autres versions de Windows. Pour s'en assurer, se reporter au livre de référence [WI] qui ne manque pas d'indique les éventuelles différences.

Avant d'entrer dans le vif du sujet, il faut encore dire que notre préoccupation est la sécurité et non les performances. De plus, puisque nous nous intéressons à des techniques employées en mode utilisateur, nous ferons souvent abstraction du fonctionnement interne de Windows pour nous concentrer sur le point de vue d'une application lancée par un utilisateur limité.

2.2. Première approche

Cette partie s'inspire fortement des chapitres 1 et 2 de [WI], ainsi que du chapitre 3 de [REV] qu'il est recommandé de lire entièrement avant de passer à la suite.

2.2.1. Système d'exploitation

L'OS exécute les tâches fondamentales telles que:

• Allocation de la mémoire principale (RAM)

• Ordonnancement des tâches

• Contrôle des entrées / sorties

• Communication via le réseau

• Gestion du système de fichiers

• Affichage graphique et système de fenêtrage

L'OS gère l'accès aux ressources de la machine et constitue un passage obligé vers le matériel. Il faut se rappeler que Windows s'appuie sur deux modes d'exécution de code qui sont implémentés matériellement dans le processeur (Intel ou compatible) :

(7)

• Un mode noyau (kernel ou ring-0), qui est privilégié et permet d'exécuter n'importe quelle instruction.

• Un mode utilisateur (user ou ring-3) ou mode non privilégié, qui restreint la liberté du programme.

L'architecture de Windows est composée de multiples couches, dont nous voyons les principales à la Figure 1 ci-dessous.

Figure 1: Architecture en couches de Windows

© 2005 by D. Solomon & M. Russinovich

Le noyau, l'exécutif Windows et les pilotes de périphériques tournent en mode noyau dans le même espace d'adresses. Un pilote peut donc consulter (et éventuellement corrompre) toutes les structures de données de l'OS.

Il faut être bien conscient de cette barrière entre le mode user et le mode kernel. Une application utilisateur emploie les services de l'OS et

(8)

manipule les ressources du système à travers des interfaces de programmation déterminées par Microsoft (API). Selon le principe de l'encapsulation, ce n'est pas à l'utilisateur de se préoccuper quel composant de l'OS fait quoi et comment, mais plutôt qu'est-ce qui lui est mis à disposition par l'intermédiaire de l'API.

2.2.2. L'API Windows

L'API (Application Programming Interface) Windows est l'interface de programmation du système. Sa version 32 bits est appelée API Win32, tandis que l'API Windows regroupe aussi les architectures 64 bits.

Chaque version de l'OS implémente un sous-ensemble différent de l'API Windows et met ainsi à disposition plusieurs milliers de fonctions. Il faut bien distinguer :

• Les fonctions de l'API Windows, sous-programmes documentés et appelables. Par exemple : les fonctions CreateProcess, CreateFile et GetMessage.

• Les fonctions natives du système: non documentées mais appelables en mode utilisateur, ces sous-programmes sont sous- jacents à l'API Windows. Par exemple, la fonction Windows CreateProcess appelle le service interne NtCreateProcess, qui transmet l'appel au noyau avec un passage en mode kernel.

Bien qu'une application puisse appeler directement tant les fonctions de l'API que les fonctions natives, ces dernières sont à éviter car elles sont sujettes à des modifications. Nous retrouvons le principe de l'encapsulation : seules les fonctions de l'API Windows sont garanties (nous en reparlerons au § 3.5.1).

2.2.3. Concepts fondamentaux

Posons quelques définitions primordiales. Nous reviendrons en détails sur tous ces concepts l'un après l'autre par la suite.

Un programme (program) est une séquence statique d’instructions. On peut exécuter plusieurs instances du même programme simultanément en mémoire, alors qu’il n’y a qu’une seule image (fichier) sur le disque.

Un processus (process) est un conteneur stockant les ressources utilisées lors de l’exécution de l’instance d’un programme. Un processus Windows comporte1 :

1. Un espace d’adresses virtuelles ou EAV (virtual address space).

Le mécanisme de mémoire virtuelle donne l’illusion au processus de disposer d’un grand espace d’adresses pour lui seul, comme s’il était le seul à s’exécuter en mémoire. De plus, ce mécanisme isole les processus entre eux. Telle adresse d'un processus peut désigner une structure de données quelconque, tandis que la même adresse

1 Cf. [WI] p. 6

(9)

dans un autre processus peut désigner tout autre chose, ou bien être invalide. Ainsi, les processus n’entrent pas en collision l’un avec l’autre et ne peuvent pas non plus corrompre les données du système d’exploitation.

2. Un programme exécutable. Il définit le code et les données de départ. Il est inséré (mapped) dans l’espace d’adresses virtuelles.

Son chargement en mémoire est effectué par le chargeur de programme (loader).

3. Un ou plusieurs threads d’exécution (voir ci-dessous).

4. Une liste des ressources, telles que sémaphores, ports de communication et fichiers, utilisées par le programme et accessibles à tous les threads. Ces ressources ne sont pas manipulables directement par leur adresse, mais à travers le système d’exploitation par un numéro appelé handle (voir § 2.4.3).

5. Un identificateur unique appelé process ID ou PID (nommé client ID dans les structures internes de Windows).

6. Le PID de son père, c'est-à-dire du processus qui l'a démarré.

7. Un contexte de sécurité nommé jeton d’accès (access token) qui identifie l’utilisateur, les groupes de sécurité ainsi que les droits associés au processus.

Un module est une instance d'une image en mémoire ; nous pouvons trouver dans la RAM plusieurs instances du même programme. Il peut s'agir d'un exécutable (fichier EXE), d'une DLL ou autre (NLS, OCX, SCR, etc.).

Un thread modélise un flux de contrôle exécutant un programme. Il appartient à un processus qui peut en contenir plusieurs. Tous les threads d’un même processus ont accès à toutes les ressources du processus et partagent le même espace d’adresses virtuelles. C’est l’ordonnanceur (scheduler) de Windows qui répartit le(s) processeur(s) entre les threads, i.e. décide où, à quel moment et jusqu’à quand chaque thread peut s’exécuter. Un thread Windows englobe2 :

1. L’état du processeur (contexte), c’est-à-dire le contenu d’un ensemble de registres de ce dernier. Il contient en particulier le compteur ordinal (program counter), qui pointe vers la prochaine instruction à exécuter.

2. Deux piles, pour l’exécution du thread respectivement en mode kernel et en mode user.

2 Cf. [WI] p. 12

(10)

3. Une zone de stockage privé appelé thread-local storage (TLS), pour être utilisée par les sous-systèmes3 (Windows, OS/2, POSIX), les bibliothèques d’exécution et les DLLs.

4. Un identificateur unique appelé thread ID (également nommé client ID dans les structures internes de Windows).

5. Parfois, un thread dispose de son propre contexte de sécurité.

Une DLL (dynamic-link library ou dynamically linked library) est une :

• Bibliothèque (library), i.e. un ensemble de routines utilisées pour développer du logiciel. Une bibliothèque contient du code et des données qui fournissent des services à des programmes indépendants. Ce code et ces données peuvent ainsi être partagées et modifiées de façon modulaire (on peut modifier leur implémentation en conservant l’interface externe).

• Liée : un lien est simplement une référence entre un exécutable et une bibliothèque, permettant au premier d’utiliser la seconde.

• Dynamiquement : la bibliothèque est présente une seule fois sur le disque dur et en mémoire, économisant ainsi de l’espace. Par opposition, une bibliothèque statique est insérée dans chaque exécutable qui l'utilise par l’éditeur de liens (linker). Un programme peut référencer une DLL lors de son chargement (loadtime) ou de son exécution (run-time).

Le système d’exploitation fournit la majorité de ses services sous forme de DLLs.

2.3. Format PE, occupation mémoire et DLLs

Pour présenter les diverses notions à aborder, nous allons étudier un cas pratique, depuis son stockage sur le disque jusqu'à son exécution en mémoire. L'application choisie est le Wordpad, qui se trouve dans

"%ProgramFiles%\Windows NT\Accessories\wordpad.exe".

2.3.1. Format PE

Il faut d'abord étudier l'image au format PE, c'est-à-dire le fichier tel qu'il est stocké sur le disque. C'est instructif car, comme le montre la Figure 2, on retrouve la structure du fichier PE (en vert) dans la mémoire (en violet). Les mêmes sections y sont présentes dans le même ordre, elles sont simplement alignées (espacées) différemment.

3 Pour les sous-systèmes, voir [WI] p. 53ss. Nous considérons uniquement le sous- système Windows.

(11)

Figure 2 : Fichier PE en mémoire

© 2002 by Marc Pietrek Sources

Eldad Eilam donne un bon survol du format PE dans [REV] pp. 93-102.

Pour un aperçu plus détaillé, se référer à l'article en 2 parties de Mark Pietrek intitulé "An In-Depth Look into the Win32 Portable Executable File Format" :

1ère partie : http://msdn.microsoft.com/msdnmag/issues/02/02/PE/

2ème partie : http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/

Enfin, les spécifications officielles des formats PE & COFF (structure détaillée et valeurs des champs) se trouvent à l'URL suivante :

http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

Outils

• PE Browse Professional, de SmidegonSoft (fait aussi office de désassembleur) :

http://www.smidgeonsoft.prohosting.com/pebrowse-pro-file-viewer.html

• LordPE4 écrit par y0da : http://y0da.cjb.net/

• Une liste d'outils (dont PESam) pour manipuler les fichiers PE : http://programmerstools.org/taxonomy/term/56

Analysons le fichier PE wordpad.exe à l'aide de PE Browse Pro. Nous passons sur l'en-tête DOS (DOS Header), qui se contente d'afficher le message d'erreur "This program cannot be run in DOS mode" si l'exécutable est démarré depuis MS-DOS. Dans la partie gauche (Figure 3) se trouve les différentes parties du fichier. Elles peuvent être

4 Installation : extraire LordPE Deluxe (http://scifi.pages.at/yoda9k/LordPE/LPE- DLX.ZIP) puis remplacer le fichier LordPE.exe par celui contenu dans la micro-update depuis http://scifi.pages.at/yoda9k/LordPE/LPE-DLXb_UPD.zip

(12)

parcourues en choisissant Dump (données brutes), Details ou encore Structure dans le menu contextuel de chaque entrée.

Figure 3 : En-tête de fichier (PEBrowse Pro)

Regardons les détails de l'en-tête de fichier COFF. Nous voyons dans Number of Sections la présence de 3 sections, et dans Characteristics les attributs suivants :

• "File is executable": le fichier est un exécutable (EXE), par opposition à une DLL qui n'est pas conçue pour être exécutée seule.

• "Relocation info stripped…" : les informations de relogement (relocation) ne sont pas présentes. Ces informations sont nécessaires lorsque le module est chargé à une adresse différente de son adresse de départ favorite (ADF) ; on dit qu'il est relogé.

Puisqu'en principe l'exécutable est chargé en premier dans l'EAV, il est chargé à son ADF. Par contre, ce n'est pas toujours le cas pour les DLLs.

• "Line number / Local symbols stripped…" : le format PE dérive du format COFF qui concerne les fichiers objets, produits de la compilation. Certains champs (table de symboles, numéro de ligne) ne sont pas pertinents pour les fichiers PE.

(13)

Figure 4 : En-tête facultatif (PEBrowse Pro)

L'en-tête facultatif (optional header) fournit des informations au chargeur de programmes, dont (Figure 4) :

• "Image Base" : valeur de l'adresse de chargement favorite.

• "Address of Entry Point" : l'adresse du point d'entrée, c'est-à- dire de la 1ère instruction machine à exécuter. Ce champ est facultatif pour les DLLs et peut valoir zéro.

• "Checksum" : il s'agit d'une somme de contrôle d'intégrité5 du fichier image. Sont vérifiés au chargement le checksum de tous les pilotes (mode kernel), de toute DLL chargé au démarrage (boot) et de toute DLL chargée dans un processus Windows critique. En bref, Windows ne contrôle pas l'intégrité de toutes les images.

• "Subsystem" : nous apprend que cette image est prévue pour le sous-système Windows (et non POSIX ou autre) et que l'interface homme-machine est en mode graphique (GUI = Graphical User Interface) par opposition au mode caractère (appelé par Microsoft CUI = Console User Interface).

5 Les spécifications ne mentionnent pas l'algorithme employé. Les fonctions de calcul de checksum sont incorporées à la DLL imaghelp.dll, documentée ici :

http://msdn2.microsoft.com/en-us/library/ms680181.aspx

(14)

• "Stack / Heap Reserve & Commit" : quantités de mémoire réservées et "engagées"6, respectivement pour la pile (stack) et le tas (heap).

Voyons maintenant les sections, qui sont les unités fondamentales de code ou de données du fichier PE. Leur nom est libre même si l'éditeur de liens Microsoft attribue des noms éloquents débutant par un point.

La section .text contient habituellement le code et les données, la section .data les variables (initialisées ou non) et la section .rsrc les ressources (menus, icônes, bitmaps, curseurs, boîtes de dialogues, etc.).

Chaque section a ses propres attributs (characteristics) qui indiquent notamment si elle contient du code, est accessible en lecture et/ou écriture, est exécutable ou est partageable. La Figure 5 montre les attributs des 3 sections de wordpad.exe. Dans notre cas, aucune de ces sections n'est initialement partageable avec d'autres processus.

Figure 5 : Attributs de sections (PESam)

Pour comprendre le mécanisme de chargement, il faut bien distinguer entre une adresse virtuelle relative (RVA = relative virtual address) en mémoire et un pointeur (offset) vers les données de l'image sur le disque. Dans le cas d'une valeur relative, il faut toujours se demander quel est le point de référence : début du fichier, ADF, début de section.

Sans parler des contraintes sur la taille des sections, remarquons juste que leur taille en mémoire (VirtualSize) peut dépasser celle sur le disque (Size of Raw Data). Le complément est simplement rempli avec des zéros ; il pourrait correspondre à des variables non-initialisées.

6 [WI] p. 384 : la mémoire virtuelle d’un processus est divisée en pages qui peuvent être libres (free), réservées (reserved) ou engagées (committed). Réserver une page revient seulement à réserver une plage d’adresses consécutives ; on ne peut pas encore y accéder. Une fois que la mémoire est committed, on peut y accéder, i.e. il y a une traduction possible entre mémoire virtuelle et mémoire physique.

(15)

Figure 6 : Détails de la section .data (PEBrowse Pro)

Laissant de côté les ressources et les informations facultatives de débogage (debugging information), voyons encore la table d'imports, qui est d'une importance capitale.

2.3.2. Import Address Table

Un module peut employer une fonction externe, respectivement mettre à disposition une de ses fonctions grâce au mécanisme d'imports / exports. Un symbole exporté est identifié par son nom ou un nombre ordinal. Typiquement, les exécutables n'exportent pas de fonctions mais se contentent d'en importer depuis diverses DLLs.

A priori, une application ne peut pas être sûre de l'adresse à laquelle une DLL est chargée. Pour utiliser une fonction importée, un module doit connaître l'adresse virtuelle (VA) de cette fonction à l'intérieur de la DLL, qu'il peut ensuite appeler via un pointeur de fonction. Il faut à un moment ou un autre opérer la liaison entre le module utilisateur et le module fournisseur : c'est ce qu'on appelle le binding.

Il existe plusieurs types de binding7 qui peuvent être mis en œuvre conjointement pour les applications :

7 Il y encore une autre forme d'import appelé "forward" dans le cas où une DLL met à disposition une fonction d'une DLL tierce.

(16)

1. "Explicit import" : c'est l'application qui demande explicitement l'adresse des fonctions importées. Nous trouvons dans le code des appels aux fonctions de l'API LoadLibrary et GetProcAddress.

2. "Implicit import" : le binding est effectué implicitement lors du chargement en mémoire. C'est le chargeur de programmes Windows qui appelle automatiquement les fonctions mentionnées en 1.

3. "Bound import" : c'est une variante de 2. On fait l'hypothèse que la DLL de la version connue à la compilation sera bien chargée à son ADF et on code en dur l'adresse de ses fonctions utilisées dans l'image de l'EXE lors de l'édition de liens.

Le chargeur de programme vérifie l'hypothèse lors du chargement de l'EXE. Si elle est infirmée (adresse de départ ou version différentes), les pointeurs de fonction sont faux et il faut procéder comme en 2. Si elle est confirmée, le binding est déjà fait, ce qui gagne du temps.

4. "Delayload import": pour améliorer les performances, le binding est effectué juste avant le premier appel à la fonction importée.

Cette variante, implémentée dans le CRT (C runtime), est transparente pour Windows.

Ce type d'import est aussi appelé lazy binding, car on ne fait les choses qu'au dernier moment. Il a pour but d'accélérer le chargement d'une application (qui peut être long en raison du chargement récursif des DLLs, voir dépendances § 2.3.5).

Ce qu'il est important de comprendre, c'est que dans tous les cas à l'exception du 1, l'adresse de la fonction importée est stockée8 dans une table appelée IAT (import address table).

En fait, la variante 4 utilise une autre table similaire : la DIAT. Mais dans les cas 2, 3 et 4, tous les appels à une fonction importée passent par un pointeur de fonction stocké dans une table. Voyons comment à la Figure 7 ci-dessous.

8 Attention! Il faut distinguer l'IAT sur le disque de l'IAT en mémoire. Selon le type d'import, ses pointeurs de fonctions sont soit chargés depuis le fichier image, soit initialisés dynamiquement.

(17)

Figure 7 : Appel à une fonction importée (PEBrowse Pro)

Nous constatons dans la partie gauche que wordpad.exe référence des fonctions provenant de 10 DLLs, listée dans l'arbre des imports. Prenons l'exemple de la fonction ADVAPI32.DLL!RegCloseKey (qui ferme une clé de la base de registres). Nous pouvons dénombrer dans la fenêtre en haut à droite 5 références à cette fonction, c'est-à-dire que le code contient 5 appels vers cette fonction.

En désassemblant le code de ces appels (en bas à droite), nous constatons qu'il est toujours identique:

CALL DWORD PTR [ADVAPI32.DLL!RegCloseKey]

Cette instruction machine en fait appelle le pointeur de fonction stocké dans une certaine entrée de l'IAT (à l'adresse 0x0100'1004 dans notre cas). Modifier ce seul pointeur permet donc de dérouter tous les appels à cette fonction importée!

En anticipant sur la suite, indiquons simplement comment afficher le contenu de l'IAT en mémoire. Il nous faut trouver son adresse en mémoire, en ajoutant la RVA de l'IAT à la valeur de l'ADF de la DLL.

Nous avons déjà vu comment extraire l'ADF ou base address. Pour trouver la RVA de l'IAT, nous allons lancer un autre outil : LordPE.

Cliquons sur le bouton PE Editor, puis ouvrons le fichier PE voulu, wordpad.exe en l'occurrence. Enfin, cliquons sur Directories pour obtenir la fenêtre suivante (Figure 8) qui liste des couples (RVA, taille) pour divers répertoires d'informations.

(18)

Figure 8 : Répertoires de données, dont IAT (LordPE)

Calculons maintenant l'adresse virtuelle en mémoire de l'IAT : Virtual Address = base adress + RVA

= 0x1000'0000 + 0x0000'1000 = 0x1000'1000

Puisqu'un pointeur tient sur 32 bits soit 4 octets, la première entrée se trouve à l'adresse virtuelle 0x1000'1000, la seconde à la VA 0x1000'1004, et ainsi de suite.

Déboguons maintenant l'application wordpad.exe avec OllyDbg (dont nous reparlerons sous peu). Ouvrons la fenêtre Memory (Alt+M), choisissons Dump dans le menu contextuel (clic droit) puis allons à l'adresse voulue (Ctrl+G).

Enfin, sélectionnons la représentation de données suivantes (clic droit) : Long -> Address. OllyDbg affiche maintenant une adresse 32 bits par ligne, en effectuant même la résolution des noms de fonction. Voici le début de l'IAT de Wordpad à la Figure 9. Nous trouvons l'adresse virtuelle de la fonction Advapi32!RegCloseKey dans la deuxième entrée.

Remarquons que l'emplacement mémoire 0x1000'1020, qui vaut zéro, marque la limite entre les fonctions importées depuis 2 DLLs, ici advapi32.dll et gdi32.dll.

(19)

Figure 9 : Dump mémoire de l'IAT (OllyDbg)

Pour terminer, disons qu'il existe de nombreux outils offrant de manipuler les fichiers PE. On peut bien sûr faire ce qu'on veut avec un éditeur hexadécimal, mais c'est moins pratique... Outre l'analyse des champs, ces outils permettent notamment de:

• Changer le point d'entrée d'un EXE, c'est-à-dire l'adresse de la 1ère instruction à exécuter

• Modifier du code machine existant (patching)

• Remplacer une fonction importée d'une DLL par une autre

• Repérer un espace libre (rempli de zéros) où insérer du code

• Si une section n'est pas exécutable, modifier ses attributs de section

• Ajouter une section pour créer plus d'espace disponible

Toutes ces modifications statiques ont un impact réel sur la sécurité mais elles impliquent d'altérer le fichier sur le disque. Elles peuvent donc être détectées aisément, par exemple par un pare-feu qui maintiendrait une table d'empreintes (MD5, SHA-1 ou autre) de ces fichiers.

2.3.3. Occupation mémoire : théorie

Chaque processus s'exécute dans un espace d'adresses virtuelles (EAV) de 4 Go. Les adresses de 0x0000’0000 à 0x7FFF’FFFF correspondent à la zone utilisateur (propre à chaque processus), alors

(20)

que les adresses de 0x8000’0000 à 0xFFFF’FFFF correspondent à la zone système (commune à tous les processus).9

Figure 10 : Espaces d'adresses virtuelles des processus et du système

© 2005 by D. Solomon & M. Russinovich

A noter que la limite entre espace utilisateur et espace système peut être déplacée à 3 Go en changeant les options de démarrage de Windows (cf. [WI] p. 15).

Chaque processus dispose de son propre EAV qui est une vue logique sur la mémoire. Par exemple, l'adresse 0x0BAD'BEEF d'un processus pointe sur une fonction de l'exécutable, alors que la même adresse d'un autre processus pointe sur une structure de données ou est invalide (n'a pas de correspondance physique). Ceci est possible grâce à un mécanisme de traduction dont nous ferons abstraction.

Figure 11 : Traduction entre mémoire virtuelle et mémoire physique

© 2005 by D. Solomon & M. Russinovich

9 Cf. Virtual Address Space dans MSDN :

http://msdn.microsoft.com/library/en-us/memory/base/virtual_address_space.asp

(21)

Par conséquent, les processus sont isolés entre eux : le processus A ne peut pas accéder directement à la mémoire (virtuelle) du processus B.

Un processus peut partager explicitement de la mémoire au moyen de fichiers mappés en mémoire (memory mapped files) ou de sections partagées. Néanmoins, un processus détient l'accès total à ses processus fils créés grâce à la fonction CreateProcess10.

De plus, un processus s'exécutant en mode utilisateur ne peut accéder à l'espace système (les 2 Go supérieurs). S'il tente de le faire, il y aura une violation d'accès. En voici une démonstration : considérons le code assembleur suivant qui va lire le mot de 32 bits se trouvant à l'adresse 0x8001'0000 (qui est l'adresse de chargement favorite de HAL.DLL, une well-known DLL chargée lors du boot).

__asm {

mov eax, 0x80010000 // EAX <- adresse de base de HAL.DLL mov ebx, dword ptr [eax] // Violation d'accès en lecture call dword ptr [eax] // Idem lors de la tentative d'appel mov dword ptr [eax], ecx // Violation d'accès en écriture }

Insérons ce code dans Visual Studio 2005 au sein du fichier source d'un nouveau projet C++ "Win32 Console Application", exécutons-le puis déboguons-le pour avoir les détails de l'exception. Voici le résultat:

Figure 12 : Violation d'accès à l'espace système (Visual Studio 2005)

Nous avons dit que l'espace d'adresses virtuelles (EAV) s'étend sur 4 Go. Mais la quantité de mémoire physiquement présente sur la machine peut varier : par exemple, mon PC de test comporte seulement 1 Go de RAM.

Pour y remédier, le gestionnaire de mémoire transfère ou pagine (pages out) des portions de la mémoire (appelée pages) sur le disque pour

10 Cf. "Process Security and Access Rights" dans MSDN :

http://msdn.microsoft.com/(...)/dllproc/base/process_security_and_access_rights.asp

(22)

libérer de l'espace en mémoire. Sitôt qu'un thread doit accéder à une page qui se trouve sur le disque, le gestionnaire de mémoire doit à nouveau charger (page in) la page en mémoire. Si toute la mémoire est occupée, alors une autre page doit être paginée pour libérer de l'espace pour la première11.

Ainsi, nous ne pouvons pas prédire si le contenu de telle portion de la mémoire virtuelle se trouvera réellement en mémoire physique ou sur le disque dur. Mais nous n'avons pas besoin de nous en soucier car le gestionnaire de mémoire de Windows s'en charge d'une façon transparente au programme utilisateur.

La page est la plus petite unité de protection au niveau matériel, c'est- à-dire qu'on peut uniquement définir les droits d'accès par page, et pas pour chaque adresse. Selon nos hypothèses (architecture x86 avec Windows XP), les petites pages font 4 ko.

En mode user, on ne peut allouer qu'un multiple de la taille de page (4 ko). De plus, l'allocation de mémoire doit respecter une certaine granularité12. Cela veut dire que l'adresse de départ des blocs de mémoire alloués doit être un multiple de 64 ko. Ces deux paramètres du système, la taille de page et la granularité d'allocation, peuvent être déterminé grâce à l'API GetSystemInfo :

SYSTEM_INFO sSysInfo;

GetSystemInfo( &sSysInfo ); // Initialise la structure.

DWORD dwPageSize = sSysInfo.dwPageSize;

DWORD dwAllocGran = sSysInfo.dwAllocationGranularity;

En passant, mentionnons que la pagination de la mémoire peut faire en sorte que le contenu de la mémoire se retrouve sur le disque. Les pages qui ont été sorties de la mémoire sont stockées dans \pagefile.sys. De même, lors d'une mise en veille prolongée (hibernation), le contenu entier de la RAM est transféré dans \hiberfile.sys. Ce phénomène peut compromettre la confidentialité des informations présentes en mémoire à ce moment.

2.3.4. Occupation mémoire : pratique

OllyDbg13, que nous avons aperçu plus haut, est un excellent débogueur gratuit. Il est important d'en maîtriser les diverses fenêtres pour savoir où trouver les informations sur le processus débogué. Tout d'abord, ouvrons l'exécutable du Wordpad. Etudions ce qu'il vient de se passer

11 Cf. [WI] p. 15

12 Il existe aussi des grandes pages de 4 Mo (large page memory) pour des questions de performances. Rechercher "large page support" dans MSDN pour aller plus loin.

13 OllyDbg (Olly pour les intimes) est écrit par Oleh Yuschuk et téléchargeable sur : http://www.ollydbg.de/

(23)

lors de l'ouverture de l'EXE. Nous avons à disposition un journal des événements (log) qui s'ouvre en pressant Alt+L. Nous le voyons à la Figure 13.

Figure 13 : Journal des événements initial (OllyDbg)

Outre la création du processus (new process) et de son thread principal (main thread), le journal initial indique le chargement d'une liste de modules avec leur adresse virtuelle de base en regard. C'est bien l'EXE qui est chargé en premier, puis il est suivi d'une série de DLLs.

La dernière ligne (Program entry point) indique que le programme est complètement chargé et prêt à s'exécuter. Le pointeur d'instruction EIP stocke désormais le point d'entrée du programme qui est 0x0101'9B40 dans notre cas. Nous nous trouvons juste avant le début de l'exécution.

Intéressons-nous de plus près aux modules (EXE et DLLs) cités ici. Pour ce faire, affichons la vue Executable modules en pressant Alt+E (voir Figure 14).

Le chargeur de programme (Windows loader) charge d'abord en mémoire l'exécutable à son adresse de départ favorite (ADF). Puis les DLLs nécessaires sont chargées à leur tour, si possible à leur ADF, sinon le chargeur procède à un relogement (relocation).

Nous pouvons vérifier, à l'aide de l'un des éditeurs de fichier PE présentés ci-dessus, qu'aucun des modules n'a été relogé. En effet, toutes ces DLLs font partie de Windows et leurs concepteurs ont choisi leur ADF de sorte que ces dernières ne se collisionnent pas dans l'EAV, évitant un relogement coûteux en temps.

(24)

De plus, nous constatons que tous ces modules respectent les contraintes d'alignement. Leur adresse de base est alignée sur 64 ko (en hexadécimal, les 4 chiffres de droite sont à zéro), tandis que leur taille est un multiple de 4 ko (les 3 chiffres de droites sont à 0).

Figure 14 : Liste des modules exécutables (OllyDbg)

Nous pouvons aussi afficher une vue complète du mapping mémoire de la zone utilisateur. Pour ce faire, ouvrons la fenêtre Memory map en pressant Alt+M (voir Figure 15). Il est possible ici de savoir à quoi correspondent les adresses 0x0000'0000 à 0x7FFF'FFFF. L'observateur attentif découvrira qu'il y a parfois des plages d'adresses invalides entre deux zones de mémoire (i.e. 2 lignes); ceci est dû aux valeurs des contraintes d'alignement et au fait que tout l'espace virtuel n'est pas occupé.

Nous retrouvons, pour chaque module, l'en-tête PE et les diverses sections. A noter que les attributs de protection de page, affichés dans la colonne Access, semblent n'être pas actualisés14. En comparant ce qui est montré dans OllyDbg avec les attributs des sections contenu dans le fichier PE (cf. l'outil PESam), nous voyons bien qu'il y a un problème.

Par exemple, les sections .text devraient être dénotées comme exécutables (E).

14 Ce bug est mentionné dans le forum d'OllyDbg à l'adresse suivante : http://www.woodmann.com/forum/showthread.php?t=8698

(25)

Figure 15 : Occupation initiale de la mémoire (OllyDbg)

Mais il y a deux choses intéressantes à remarquer concernant les modules. Premièrement, toutes ces DLLs se trouvent dans

\Windows\system32, sauf une qui provient de \Windows\WinSxS\. Cette dernière DLL trahit un mécanisme introduit dans Windows XP pour gérer simultanément plusieurs versions d'une même DLL15. En fouillant le répertoire \Windows\WinSxS, on trouve (Figure 16) 3 dossiers qui contiennent chacun la DLL comctl32.dll, avec les versions respectives 6.0.0.0, 6.0.10.0 et la version utilisée par wordpad.exe : 6.0.2600.2180.

Figure 16 : Versions concurrentes d'une DLL (Explorer)

15 Pour plus d'informations, voir l'encart "Side-by-side Assemblies" dans [WI] p. 311.

(26)

Deuxièmement, pour l'instant, seules les DLLs importées implicitement sont présentes dans l'EAV. Or nous pouvons compter que 13 DLLs ont été chargées, tandis que le fichier PE n'en référence que 10 (voir Figure 7) ! Comment cela se fait-il? Il faut aborder le problème des dépendances.

2.3.5. Dépendances

Nous avons déjà parlé au § 2.3.2 des différents types d'imports. On dit que l'exécutable dépend des DLLs qu'il importe. Mais ces dernières dépendent aussi peut-être d'autres DLLs. Ainsi le chargeur de programmes doit-il vérifier ces dépendances récursivement afin de charger toutes les DLLs nécessaires.

L'utilitaire Dependency Walker16 trace les dépendances d'une image (EXE ou DLL). Pour mieux comprendre ce qu'il affiche, consulter l'aide de l'application, en particulier la rubrique "Types of Dependencies Handled…". En ouvrant wordpad.exe avec depends, nous pouvons faire les observations suivantes sur la Figure 17 :

1. La zone en haut à gauche montre l'arbre des dépendances. La racine est sans surprise l'image étudiée. Le second niveau comporte toutes les DLLs importées implicitement (les 10 référencées implicitement dans le fichier PE), et ainsi de suite. La relation père-fils dénote la dépendance.

2. En développant le sous-arbre d'ADVAPI32.DLL, nous trouvons d'abord deux DLLs dupliquées (c'est-à-dire qui ont déjà été identifiées plus tôt lors du parcours de l'arbre) NTDLL.DLL et KERNEL32.DLL.

3. Puis nous rencontrons RPCRT4.DLL, qui ne fait pas partie des 10 DLLs mentionnées plus haut mais qui se trouve dans la Memory Map initiale de OllyDbg. Cette DLL est chargée parce qu'ADVAPI32.DLL en a besoin pour fonctionner.

4. Les deux dernières DLLs sont à chargement retardé, d'où le symbole de sablier présent dans leur icône ( ). WINTRUST.DLL et SECURE32.DLL ne seront chargées qu'au moment où elles seront réellement utilisées. Elles sont actuellement absentes de l'EAV du Wordpad, puisqu'il n'y a aucun appel à des fonctions de ces DLLs avant que l'exécution du code du processus ne débute.

16 L'exécutable depends.exe est fourni avec les Windows XP Service Pack 2 Support Tools, téléchargeable sur http://www.microsoft.com.

(27)

5. Dans la partie médiane se trouve une liste de toutes les DLLs importées par le module wordpad.exe, directement ou non. Nous pouvons ignorer l'avertissement affiché en rouge en bas de la fenêtre. S'il y avait vraiment un problème de fonction non résolue, alors l'exécution de Wordpad le signalerait.

6. Les deux zones semblables en haut à droite sont des listes de fonctions. La liste du bas contient les fonctions exportées par la DLL qui est sélectionnée dans l'arbre des dépendances (à gauche). La liste du haut indique les fonctions effectivement importées par la DLL parente de la DLL actuellement sélectionné. La liste du haut contient donc un sous-ensemble des fonctions de la liste du bas.

Figure 17 : Dépendances de DLLs et fonctions importées (Dependancy Walker)

(28)

Par la suite, d'autres DLLs seront chargées dynamiquement, à cause d'un appel soit à l'API LoadLibrary, soit à des fonctions de delay-load DLLs17. Si nous lançons le programme en pressant sur F9 et que nous ouvrons la fenêtre Log, nous voyons le chargement (ligne débutant par Module) et le déchargement de DLLs (ligne débutant par Unload).

Remarquons encore à la Figure 18 que le module hpcjrui.dll est chargé puis déchargé 3 fois de suite. Cette dernière fenêtre nous amène à la partie suivante, qui est centrée sur l'exécution du programme par les threads.

Figure 18 : Journal des événements suivant (OllyDbg)

2.4. Processus, threads et handles 2.4.1. Processus

Nous allons nous pencher maintenant sur l'exécution de wordpad.exe.

Un processus est un objet vivant, qui naît, évolue puis meurt. Il ne suffit pas de disposer des bons outils pour l'observer, il faut encore regarder au bon moment. Pour cette partie, la référence est le chapitre 6 de [WI], intitulé "Processes, Threads and Jobs". Nous ne parlerons pas des jobs.

17 Il s'agit de DLLs qui sont chargées juste avant le premier appel à l'une de leurs fonctions, comme nous l'avons évoqué à la section sur l'IAT. Il s'agit d'un mécanisme paresseux (lazy binding), qui ne fait le travail que lorsqu'il est vraiment nécessaire.

Le fait de retarder le chargement de DLLs permet à l'application de démarrer plus vite.

(29)

Le premier outil à notre disposition est le gestionnaire de tâches de Windows (Task Manager). Sous l'onglet Processes, nous pouvons voir tous les processus existants, leur PID, l'utilisateur qui les a lancés, leurs compteurs de threads et de handles, etc. (aller dans le menu View | Select columns pour afficher toutes les informations).

Remarquons en passant les valeurs que prennent les PIDs. Sous UNIX, les PIDs se sont attribués consécutivement (0, 1, 2, 3, etc.) et indiquent la chronologie des créations de processus. Sous Windows au contraire, les PIDs ne peuvent être prédits et ne présentent aucun ordre intelligible hormis le fait qu'ils sont des multiples de 4.

Sur la Figure 19, la valeur des PIDS varie de 0 à plus de 1412. Le PID du pseudo-processus Idle vaut 0 ; celui de System, 4. Le processus wordpad.exe a reçu l'ID 404, bien qu'il ait été lancé le dernier.

Figure 19 : Liste des processus (Task Manager)

Présentons maintenant l'incontournable utilitaire Process Explorer18, abrégé ProcExp, qui donne accès à une mine d'informations sur les

18 Cet utilitaire, ainsi que d'autres, sont produits par SysInternals qui vient d'être racheté par Microsoft. ProcExp est téléchargeable depuis :

(30)

processus et leurs threads. Ces mêmes informations et bien d'autres sont présentées par d'autres programmes, sous d'autres formes. Pour simplifier, nous privilégierons ProcExp autant que possible.

Une des premières choses à connaître sur un processus est sa généalogie. Bien sûr, nous pouvons trouver son PID, le chemin complet de son image, ses statistiques, etc. Mais les ancêtres d'un processus sont déterminants pour expliquer ses droits.

En lançant ProcExp normalement, nous voyons l'arborescence habituelle de Windows dans la partie droite de la Figure 20. Le processus System (PID = 4) est l'ancêtre commun à tous les autres processus. Puis nous trouvons smss.exe qui gère les sessions et ensuite winlogon.exe qui opère l'ouverture de session (il y en a 2 dans notre cas, une pour la session locale et l'autre pour la session Remote Desktop).

En bas de l'arbre, nous apercevons une deuxième racine. Le processus explorer.exe semble être orphelin. En fait, cela signifie que son processus père s'est terminé, ce qui a causé une rupture de l'arbre car un processus ne connaît que son ancêtre immédiatement précédent.

Pour découvrir qui a démarré explorer.exe, il suffit de lancer automatiquement ProcExp lors de l'ouverture de session (p.ex. en copiant son raccourci dans le dossier Startup du menu Start). La nouvelle capture d'écran indique le chaînon manquant entre winlogon.exe et explorer.exe : userinit.exe, dont le rôle est précisément de démarrer le shell de l'utilisateur19.

ProcExp peut être mis en pause en appuyant sur la barre espace. Il comporte un panneau inférieur (lower pane) qui, une fois affiché (Ctrl+L), indique soit les DLLs utilisées par le processus (Ctrl+D), soit les handles (Ctrl+H). Toutes les autres informations concernant un processus sont accessibles en ouvrant ses propriétés par double clic et en naviguant parmi les onglets.

Un processus, comme tout objet géré par le système, est représenté par une ou plusieurs structures de données. Nous pourrions explorer les entrailles de Windows et utiliser LiveKd20 pour analyser ces structures de données. Mais ceci nous préparerait à des attaques en mode kernel, pas en mode user. Nous nous contenterons de renvoyer à [WI] p. 298 pour une liste des fonctions de l'API concernant les processus.

http://www.sysinternals.com/Utilities/ProcessExplorer.html

19 Cf. [WI] p. 272. Remarquons qu'il aurait pu y avoir plusieurs intermédiaires entre winlogon.exe et explorer.exe.

20 Téléchargeable depuis http://www.sysinternals.com/utilities/livekd.html

(31)

Æ

Figure 20 : Arbre des processus avec et sans userinit.exe (Process Explorer) 2.4.2. Threads

De nouveau, il y a énormément d'informations disponibles concernant les threads : statistiques d'utilisation de la mémoire et du temps CPU, priorités et changements de contexte, etc. Ce qui nous importe surtout est de savoir combien de threads s'exécutent et ce qu'ils font.

Lançons ProcExp, et affichons l'onglet Threads des propriétés du processus wordpad.exe. Nous apprenons que ce processus possède actuellement trois threads. Si nous ouvrons un fichier dans Wordpad, nous constatons que quatre threads supplémentaires sont créés.

Comme pour les processus, ProcExp signale les créations de threads en vert et les destructions en rouge.

Avant de savoir ce que font les threads, il faut configurer correctement les symboles. Vaut-il mieux constater que le thread n° 1640 est en train d'exécuter la fonction à l'adresse 0x0101'9B4D? Ou bien savoir que ce thread exécute la fonction wWinMainStartup du module wordpad.exe?

Les symboles permettent à ProcExp et aux débogueurs de retrouver la correspondance entre les adresses particulières du module et les noms de fonctions, de variables ou de classes. Leur but est de donner du sens à ces adresses aux êtres humains que nous sommes.

(32)

En plus des Debugging Tools, nous avons besoin de télécharger et d'extraire le Symbol Package21 dans le dossier %WinDir%\Symbols. Puis il faut configurer ProcExp ainsi :

Dans la boîte de dialogue Configure Symbols du menu Options, entrer les valeurs suivantes :

• "Dbghelp.dll path" :

C:\Program Files\Debugging Tools for Windows\dbghelp.dll

• "Symbols path" :

srv*C:\Windows\Symbols*http://msdl.microsoft.com/download/symbols Cela a pour effet de chercher les symboles dans l'entrepôt local, et de les télécharger depuis le serveur Microsoft au besoin. Bien sûr, ces symboles couvrent uniquement les logiciels fournis avec Windows, dont fait partie le Wordpad.

Figure 21 : Threads d'un processus (Process Explorer)

21 "Windows XP with Service Pack 2 x86 retail symbols, all languages" : http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx

(33)

Voici, à la Figure 21, les threads du processus wordpad.exe. Les quatre boutons sur la droite permettent, après avoir sélectionné un thread, de le tuer (kill) ou le suspendre (suspend), d'afficher les propriétés de l'image (module) ou la pile d'appels (stack) du thread. Il faut savoir que si un thread reçoit l'ID X, alors aucun processus n'aura le même ID, et vice versa.

Nous prendrons la pile d'appel du thread principal du processus cmd.exe, qui est plus explicite. Nous voyons le flux de contrôle de bas en haut, les appels ayant circulé depuis la fonction mainCRTStartup, puis aux fonctions main, Parser, ParserStatement, GeToken, etc.

Figure 22 : Pile d'appels d'un thread (Process Explorer) 2.4.3. Handles

Les handles sont utilisés lorsqu’une application référence des blocs de mémoire ou des objets gérés par un autre système (le système d’exploitation dans notre cas). Contrairement au pointeur qui contient littéralement l’adresse de l’élément qu’il référence, un handle est une référence abstraite.

Ainsi, l’application ne peut pas accéder directement aux données d’un objet, mais elle doit toujours passer par le système d’exploitation, ce qui permet d’accroître le contrôle des ressources et la sécurité.

Supposons que nous voulions lire un fichier. Voici la marche à suivre : 1. Ouvrir un handle sur le fichier existant via CreateFile. A cette étape

ont lieu les contrôles d'accès que nous aborderons plus loin.

2. Utiliser l'objet en passant son handle à d'autres fonctions, telles ReadFile ou ReadFileEx.

3. Fermer le handle via CloseHandle une fois son utilisation terminée.

Une application peut obtenir un handle sur toutes sortes d'objets du système. La plupart des handles ont une portée limitée au processus.

Cela veut dire par exemple que si un processus X a reçu un handle sur un fichier valant 4, et que le processus Y cherche à fermer ce même fichier en passant une valeur de handle de 4, cela ne fonctionnera pas.

(34)

En effet, le handle 4 peut être invalide pour le processus Y, ou correspondre à un autre objet (une clé de registre, un sémaphore, etc.).

Figure 23 : Handles d'un processus (Process Explorer)

Une exception à cette règle est le handle de module (HMODULE) retourné par l'API GetModuleHandle qui est égal à l'adresse de base du module. De toute manière, le module étant situé dans l'espace utilisateur, cela ne pose pas de problème de sécurité d'y accéder.

Certains numéros d'objets ont une portée globale à tout le système.

C'est le cas notamment pour les identificateurs de processus (chaque processus porte un ID distinct), de threads et de fenêtres.

Nous pouvons lister les handles d'un processus dans ProcExp ou avec l'utilitaire en ligne de commande handle22. ProcExp (voir Figure 23) est bien plus convivial. Sélectionnons le processus wordpad.exe, puis affichons les handles (Ctrl+L, Ctrl+H). Pour chaque handle, nous pouvons afficher ses propriétés en double-cliquant dessus.

ProcExp donne des informations qui ne sont pas accessibles en mode utilisateur, grâce à son pilote en mode kernel. C'est ainsi que ProcExp peut donner l'adresse (dans l'espace système) de l'objet correspondant à un handle, et d'autres renseignements selon le type de handles.

22 Disponible sur http://www.sysinternals.com/utilities/handle.html

(35)

Figure 24 : Détails des propriétés d'un handle (Process Explorer)

Remarquons pour finir que le nombre de références (voir encadré, Figure 24) est le nombre de pointeurs en mode kernel qui pointent vers l'objet en question. Ces références sont employées pour savoir quand l'objet peut être. Le nombre de handles concerne le mode utilisateur et parle de lui-même. Le second onglet des propriétés d'un handle concerne la sécurité, que nous allons étudier au § 2.5.

2.4.4. Fichiers mappés et mémoire partagée

Les fichiers mappés en mémoire ou MMFs23 (Memory-Mapped Files) offrent la possibilité d'accéder à des fichiers sur le disque aussi facilement qu'on accède à la mémoire dynamique : au moyen de pointeurs. Il suffit de mapper tout ou partie d'un fichier en mémoire, c'est-à-dire de faire correspondre les données du fichier à une plage d'adresses virtuelles.

On retrouve le principe de mémoire virtuelle : à une page en RAM correspond une page sur le disque (nous en reparlerons au § 3.4.1). Les pages de codes proviennent de l'image24 du fichier PE, tandis que les pages de données sont stockées dans le fichier d'échange du système (system pagefile). On peut créer, via l'API CreateFileMapping, un MMF correspondant à un fichier temporaire qui sera alors créé, à un fichier existant ou encore à rien du tout (i.e. au system pagefile).

23 MMF : http://msdn2.microsoft.com/en-us/library/ms810613.aspx

24 On peut se demander à juste titre ce qu'il se passe si on supprime le fichier image d'une application en exécution. La réponse est que c'est tout simplement impossible : les fichiers EXE et DLL sont verrouillés, donc impossible à effacer, tant qu'ils sont chargés par un processus.

(36)

Figure 25 : Fichiers mappés en mémoire © Microsoft

Les MMFs permettent aussi de partager de la mémoire entre deux processus25. Il faut pour cela que tous les processus utilisent le nom ou bien le handle du même MMF. Le handle peut être transmis par héritage ou par duplication. Se référer à MSDN pour un exemple de deux processus qui partagent de la mémoire nommée26.

2.5. Modèle de sécurité

Le modèle de sécurité sous Windows est complexe. Nous essayerons de survoler les notions clé et de donner un aperçu global du contrôle d'accès (authorization). La problématique de la sécurité peut être résumée ainsi :

1. Faut-il permettre ou non à tel sujet d'effectuer telle opération sur tel objet?

2. Quels événements de succès ou d'échec de ces demandes d'accès faut-il auditer dans un fichier trace (log)?

2.5.1. Utilisateur et jeton d'accès

La sécurité de Windows NT est centrée sur l'utilisateur, c'est-à-dire que les autorisations d'accès ne concernent pas les applications mais bien les comptes (accounts). Il faut donc parler de l'authentification ou comment un utilisateur prouve son identité.

Windows identifie un utilisateur par son nom et le nom de son domaine (par exemple DELL12\Florent sur notre machine de test). Au lieu des noms sous forme de chaîne de caractères, ce sont des identificateurs de

25 http://msdn.microsoft.com/library/en-us/memory/base/sharing_files_and_memory.asp

26 Creating Named Shared Memory :

http://msdn.microsoft.com/(...)/memory/base/creating_named_shared_memory.asp

(37)

sécurité ou SIDs (security identifiers) qui sont utilisés pour identifier de manière univoque des entités. Chaque utilisateur, domaine, groupe local / de domaine, et membre de domaine possède son SID27 unique.

L'utilitaire psgetsid28 permet de traduire un SID en nom complet et le contraire.

C:\>psgetsid Administrator

PsGetSid v1.42 - Translates SIDs to names and vice versa Copyright (C) 1999-2004 Mark Russinovich

Sysinternals - www.sysinternals.com SID for DELL12\Administrator:

S-1-5-21-1454471165-963894560-725345543-500

Lors d'une ouverture de session (logon) réussie, le système crée un jeton d'accès (access token) qui décrit le contexte de sécurité de l'utilisateur. Ce jeton contient29 :

• Le SID du compte utilisateur

• Les SIDs des groupes auxquels ce dernier appartient

• Eventuellement une liste de SIDs restreints

• Un tableau de privilèges (voir § 2.5.3)

Les ressources de l'ordinateur sont formalisées par des objets, gérés par le gestionnaire d'objet en mode kernel, tels : périphériques, fichiers, processus, threads, événements, ports d'E/S, éléments de synchronisation, sections de mémoire partagée, jetons d'accès, clés de la base de registre, etc.

Les sujets qui manipulent les objets sont les threads. Par défaut, un thread hérite de l'identité de son processus, mais il peut aussi usurper temporairement une autre identité30. Pour simplifier, nous ferons l'hypothèse que tous les threads d'un processus possèdent le même contexte de sécurité, à savoir celui de leur processus. Nous parlerons donc uniquement des jetons primaires (primary tokens), par opposition aux impersonation tokens.

27 Une description de la structure du SID ainsi que les valeurs particulières des SID standards (well-known) peuvent être consultées dans [WI] pp. 495-497.

28 Téléchargeable sur http://www.sysinternals.com/utilities/psgetsid.html

29 Cf. la structure de données dans [WI] p. 498.

30 Ce mécanisme, appelé impersonation en anglais, est décrit dans [WI] pp. 502ss. Il permet à un thread d'une application serveur d'endosser l'identité d'un de ses clients.

(38)

Nous avons dit plus haut que la généalogie d'un processus est importante. En voici la raison : le processus explorer.exe, qui est l'ancêtre commun de tous les processus appartenant à l'utilisateur, reçoit le jeton d'accès créé par lsass.exe, et le propage à ses descendants.

2.5.2. Droits, handles et ACLs

Le contrôle d'accès, opéré par le composant SRM (Security Reference Monitor), concerne une certaine action sur un certain objet. Disons que le rôle du SRM est de résoudre une équation qui prend en entrée l'identité (le jeton) d'un thread, le type d'accès souhaité et les paramètres de sécurité de l'objet, et qui retourne une valeur booléenne.31

Cette vérification a lieu lors de l'ouverture d'un handle où l'utilisateur doit spécifier quelles opérations il désire effectuer sur l'objet. Si l'accès est accordé, ce handle-ci ne permettra de réaliser que ces opérations-là.

Nous pouvons visualiser les handles dans ProcExp comme expliqué au

§ 2.4.3.

Les permissions d'accès à un objet sont décrites par la DACL (Discretionary Access Control List), qui comporte plusieurs ACEs (Access Control Entries). Signalons que l'ordre des entrées dans une ACL est déterminant en raison de l'algorithme de contrôle d'accès. Enfin, les événements à auditer sont décrits dans la SACL (System ACL).

Le genre d'accès souhaité dépend du type d'objet. Il est décrit par un masque d'accès (access control mask). Tous ces composants et leur fonctionnement sont présentés en détails dans MSDN, ainsi que dans une série d'articles sur le site The Code Project32.

Nous pouvons observer dans ProcExp que Worpad détient un handle sur la clé de la base de registre HKCU. En ouvrant les propriétés du handle (double-clic) puis en allant dans l'onglet Security, nous voyons la DACL de cet objet. Nous ignorons quel accès a été accordé pour le handle en question, mais nous trouvons ici l'accès maximal à cet objet qui peut être accordé à tel compte (le groupe Administrators sur la capture d'écran).

31 Cf. [WI] au bas de la p. 494.

32 The Windows Access Control Model :

http://www.codeproject.com/win32/accessctrl1.asp

(39)

Figure 26 : Propriétés de sécurité d'un handle (Process Explorer) 2.5.3. Privilèges

Certaines opérations du système, comme la sauvegarde des fichiers, serait trop compliquées à implémenter avec les droits. Par exemple, il faudrait ajouter une entrée à la DACL de tous les fichiers pour que l'opérateur de sauvegarde puisse y accéder pour les sauvegarder. Il existe donc des privilèges33 qui autorisent à outrepasser les mécanismes de sécurité usuels. Ces privilèges ne concernent pas des objets en particulier. Ils se rapportent à un utilisateur et font partie de son jeton.

Les privilèges suivants sont particulièrement puissants.

Nom du privilège Effet pour l'utilisateur SeCreateTokenPrivilege Autorise à créer un jeton primaire

SeDebugPrivilege Autorise à déboguer n'importe quel processus SeLoadDriverPrivilege Autorise à charger ou décharger un pilote

(mode kernel)

SeRestorePrivilege Autorise à restaurer (ajouter, supprimer) des fichiers et dossiers et à manipuler leurs autorisations NTFS

33 Cf. [WI] pp. 516-523. Voiri aussi la liste des "privilege constants" dans MSDN : http://msdn.microsoft.com/library/en-us/secauthz/security/authorization_constants.asp

(40)

SeSecurityPrivilege Autorise à fixer la stratégie d'audit et à manipuler le journal d'événements

SeTcbPrivilege Désigne comme faisant partie du système d'exploitation (TCB = Trusted Computer Base)

SeTakeOwnership Autorise à s'approprier des fichiers et autres objets sans y avoir accès.

Figure 27 : Tableau des privilèges puissants

Ces privilèges ou droits se retrouvent dans le jeton d'accès. Ils peuvent y être activés ou désactivés. ProcExp permet d'afficher le contexte de sécurité d'un processus dans l'onglet Security des propriétés d'un processus. Nous voyons à la Figure 28 le jeton d'accès du module wordpad.exe, lancé depuis un compte administrateur.

Figure 28 : Jeton d'un processus (Process Explorer)

Dans notre cas, l'utilisateur Florent est administrateur du système. Il dispose de nombreux privilèges même si certains d'entre eux sont

(41)

actuellement désactivés (en grisé). Le nombre de privilèges d'un processus démarré depuis un compte limité est plus petit.

L'attribution des privilèges à des utilisateurs ou des groupes est déterminée par une stratégie locale. Nous trouvons cette dernière dans Configuration Panel | Administrative Tools | Local Security Settings (ou exécuter secpol.msc). En développant Local Policies puis User Rights Assignments, nous voyons par exemple quel seul l'utilisateur Florent détient le privilège Debug (au lieu du groupe Administrators par défaut).

Figure 29 : Stratégies locales de sécurité de Windows

De plus, un jeton peut être restreint34 (restricted token), c'est-à-dire que des SIDs peuvent être désactivés ou restreints, et des privilèges supprimés. Pour observer un jeton restreint, créons un raccourci vers un exécutable quelconque, puis ouvrons les propriétés avancées (Properties | Advanced) et cochons "Execute with different credentials".

Lançons maintenant le raccourci et validons la boîte de dialogue qui s'affiche. Le jeton du processus est restreint et ne présente plus qu'un seul privilège : SeChangeNotifyPrivilege.

En constatant que certains privilèges sont présents dans le jeton, mais sont désactivés, nous pouvons penser que ces privilèges peuvent être activés. Comme l'indique Mark Russinovitch, l'idée est que les privilèges ne doivent être activés qu'au moment où on en a besoin. Par exemple, nous pouvons voir le privilège SeSystemtimePrivilege basculer lors d'un changement d'heure35.

34 http://msdn.microsoft.com/library/en-us/secauthz/security/restricted_tokens.asp

35 Cf. expérience dans [WI] p. 518s

Références

Documents relatifs

sans bobinage, qui régénèrent un signal de 19 kHz dont l'amplitude est très inférieure à celle de l'AF, et signalent, par allumage d'une diode électroluminescente,

Si vous ou le ministre Morneau pouvez faire quoi que ce soit pour nous aider à trouver des sources de capital pouvant être déployées rapidement, nous savons que notre

L’évocation des sièges de villes est presque immanquablement le fait des soldats fanfarons qui sont, de loin, les personnages les plus présents dans le théâtre comique des

Depuis 1993, les agents de santé, le personnel infi rmier et les superviseurs villageois ont bénéfi cié d’une formation en matière d’éducation et de conseil relatifs au

• La fonction « getopt » retourne la lettre de l'option courante de la ligne de commande qui correspond à l'une des lettres d'option présente dans la chaîne « optstring ». •

Mercredis avenir : découvrez le calendrier 2019- 2020 ainsi que le secteur qui inaugurera cette saison, les métiers de

cat * → afche le contenu de tous les fchiers du dossier courant. cat *.txt → afche le contenu de tous les fchiers .txt du

Supposons donc la figure construite, on peut la voir comme un pavage fait avec deux triangles isocèles de côtés oranges (noté l) et bleus (noté L)..