Forums d'entraide informatique - Astuces - Conseils
Des experts à votre écoute pour tous vos dysfonctionnements
Vous n'êtes pas identifié.
Pages: 1
- Accueil forums
- » Programmation
- » Fiabilisation d'expoit Windows - de la faille à l'expoit (2)
#1 31-08-2008 22:58:49
- Admin
- Administrateur
- Date d'inscription: 30-07-2008
- Messages: 683
Fiabilisation d'expoit Windows - de la faille à l'expoit (2)
Nous décrivons rapidement le fonctionnement de PAGEEXEC, pour plus d'informations, le lecteur pourra se reporter à la documentation de PaX [PaX]. Lorsque le processeur ne supporte pas physiquement la possibilité de marquer une page comme étant non exécutable, PaX utilise la séparation des TLB introduite avec les processeurs Pentium pour émuler cette fonctionnalité. Les TLB (Translation Lookoside Buffers) sont des caches situés dans le processeur qui contiennent les entrées récemment utilisées
des Page Directories et Page Tables afin d'accélérer la traduction d'adresses linéaires en adresses physiques. Dans les processeurs récents, les TLB sont séparés selon que l'adresse translatée est utilisée pour récupérer du code (instruction fetch) (ITLB) ou pour écrire/lire en mémoire (DTLB). De plus, c'est au noyau d'invalider explicitement les TLB lorsqu'il modifie une Page Table ou un Page Directory. Cela signifie qu'il est possible pour le système d'exploitation de contrôler de manière fine les PTE contenus dans les TLB et en mémoire. Le fonctionnement de PAGEEXEC est le suivant :
MI Les pages à rendre non exécutables sont marquées « superviseur >) (flag U/S dans le PTE).
Toute utilisation d'une telle page par le processus en mode utilisateur génère une faute. Le Page fouit handlcr du noyau vérifie si cette faute est due à une tentative d'exécution de code (en comparant El P du mode utilisateur et l'adresse de la page). Dans ce cas le processus est terminé (en réalité, les fonctionnalités EMUTRAMP et EMUSIGRT qui servent à émuler deux cas communs de génération de code à la volée, trampolines GCC et signal return du noyau sur la pile (vieilles libcs), compliquent ce processus).
111111 Dans le cas où l'accès demandé n'est pas en exécution, le PTE est modifié afin de permettre un accès à la page depuis le mode utilisateur, puis un accès est réalisé afin de charger le PTE correspondant dans le DTLB.
11111111 Le drapeau superviser est alors repositionné dans le PTE afin que tout accès en exécution soit à nouveau détecté.
MM Lorsque l'exécution du processus en mode utilisateur reprend, tout accès en lecture/écriture utilisera le DTLB et ne provoquera pas de nouvelle faute alors qu'un accès en exécution passera par le PTE avec le drapeau superviseur.
Ce procédé est relativement coûteux en temps processeur. C'est pourquoi une autre méthode, SEGMEXEC, a été introduite en 2002. Cependant, fin 2004, PAGEEXEC a été amélioré dans le noyau 2.6. Il combine maintenant pagination et segmentation : une limite au segment de code est établie, au-dessus de la page exécutable la plus basse. De ce fait, toutes les pages situées au-dessus de cette limite ne sont pas exécutables grâce à la segmentation, ce qui a pour conséquence de ne pas imposer une surcharge lors de l'utilisation de ces pages. Les pages non exécutables situées en dessous de cette limite sont traitées avec les TLB séparés, comme expliqué ci-dessus. D'un point de vue pratique, cette nouvelle méthode est relativement efficace car les « grosses » zones de données (le tas par exemple), sont situées au-dessus de la limite de cs. La surcharge n'existe que pour les zones de données qui n'ont pas pu être logées au-dessus de cette limite (par exemple les sections .data des bibliothèques). Des mesures de performances sont disponibles sur [paxperf].
SEGMEXEC
L'introduction de SEGMEXEC dans la deuxième moitié de 2002 a été un pas majeur pour PaX. Grâce à cette nouvelle méthode, les surcharges induites par PaX sont devenues négligeables, au prix cependant d'une complexité d'implémentation accrue. SEGMEXEC est une méthode ingénieuse permettant d'obtenir des pages non exécutables en
taille de l'espace des adresses logiques réellement utilisables par un processus en mode utilisateur passe de 3 Go à 1,5 Go.
L'idée est de séparer complètement le segment de code utilisateur du segment de données utilisateur. Nous l'avons vu, en temps normal les segments utilisateurs ont une base de 0 et une limite de 4 Go et se chevauchent donc parfaitement dans l'espace d'adressage linéaire : une adresse OxAABBCCDD utilisée pour lire/écrire des données ou pour exécuter du code se traduira invariablement par une adresse linéaire OxAABBCCDD. Imaginons maintenant que le segment de code et le segment de données soient disjoints dans l'espace d'adressage linéaire de la manière suivante
IM Un descripteur de segment de données « User>) (USER_DS) a pour adresse de base 0 et pour limite 1,5 Go, celui-ci est référencé par les sélecteurs chargés dans ds, es et ss
MI Un descripteur de segment de code « User » (USER CS) a pour adresse de base Ox60000000 (1,5 Go) et pour limite 1,5 Go, celui-ci est référencé par le sélecteur chargé dans le registre cs
Grâce à cette technique, une page située dans le segment de code (c'est-à-dire entre 1,5 et 3 Go dans l'espace d'adressage linéaire) sera exécutable (accessible depuis cs) mais pas lisible/inscriptible (rappelez-vous que les accès aux données se font par rapport aux sélecteurs ds, ss ou es), alors qu'une page située dans le segment de données (c'est-à-dire entre 0 et 1,5 Go dans l'espace d'adressage linéaire) ne sera pas exécutable.
Afin d'être certain que des pages situées dans le segment de données ne puissent jamais être exécutées (le but étant par la suite de pouvoir prouver que l'injection de nouveau code exécutable dans l'espace d'adressage est impossible, cf. la partie dédiée à MPROTECT), il faut s'assurer qu'un autre sélecteur de segment moins restrictif ne puisse être chargé dans cs. C'est pourquoi PaX s'assure que la GDT utilisée par les processus utilisant SEGMEXEC possède un seul descripteur de segment de code en DPL3. En effet, il ne faut pas qu'un processus contrôlé par l'attaquant (par exemple à l'aide de return-to-libc chaînés) puisse être amené à charger dans cs un descripteur de segment lui permettant d'exécuter des pages contenues dans le segment de données (on pourrait imaginer par exemple que l'attaquant exécute un far return dont l'opcode était déjà présent dans la partie exécutable de l'espace d'adressage du processus).
Avons-nous à ce stade réellement une sémantique de pages non exécutables ? Pas vraiment, car notre nouvelle protection PROT_ EXEC n'est pas ici combinable avec les protections PROT_VVRITE et PROT_READ : il n'est pas possible de lire des données qui seraient situées dans une page exécutable. Cela est bloquant; si vous faites attention à la sortie de re a del f -1 /bir /cat. ci dessus, vous verrez que la section .rodata (qui contient les données en lecture seule) par exemple se retrouve dans le segment (au sens ELF) de code. En pratique, il est, à de nombreuses occasions, nécessaire de pouvoir lire une page exécutable.
La solution à ce problème, le VMA mirroring, est ce qui rend SEGMEXEC complexe. Toute page disponible dans le segment de code doit être disponible également dans le segment de données. Pour cela, pour toute page présente dans le segment de code (adresses linéaires de 1,5 à 3 Go), un PTE « miroir>) est ajouté
dans le segment de données, référençant le même cadre de page que le PTE présent dans le segment de code (notez bien qu'il n'y a toujours qu'un cadre de page correspondant en mémoire physique !).
Notre espace d'adressage linéaire présente l'aspect suivant où l'on peut remarquer trois zones exécutables mirorées (voir également figure 7) :
08048000-0804000 r-xp 00000000 03:03 32672 /binicat [1]
094c000-08044009 rw.p 00003090 03:03 32672 /bin/cat
08944090-0806e000 rw-p 00000000 00:09 0
20000000-20016000 r-xp 00000000 03:03 179102 /lib/14-2.3.2,sa [2]
20016090-20017000 te-p 00015000 03:03 179102 /lib/14-2.3.2.so
20017009-29018000 rw-p moun mu
20014000-20145000 r-xp 00000000 03:03 179607 /lib/libc-2.3.2.so [3]
20145000-20144000 rw-p 00127000 03:03 179607 /lib/libc-2.3.2.so
20144090-20150002 rw-p 00000000 00:00 0
20150009-2019f900 r--p 00000000 03:03 942989 /usr/lib/locale/locale-archive
5fffe000-60000000 rw-p 00000000 00:00 [pile]
68048000-6804c000 r-xp 00009000 03:03 3272 /bin/cat [1]
80000000-80016000 r-xp 00900000 03:03 179102 /lib/14-2.3.2.so [2]
80014000-80145000 r-xp 00000000 03:03 179607 /lib/libc-2.3.2.so [31
De nombreuses modifications sont nécessaires afin que les deux pages « miroir » restent synchronisées en cas de changement d'état (lorsque le kernel gère un page fault, un mremap■ , un mprotect (l..), ce qui rend le VMA mirroring complexe.
Tous ces changements influent sur la segmentation et changent l'espace d'adressage linéaire.
Pour un processus, qui lui ne « voit » que l'espace d'adressage logique, rien n'a changé, si ce n'est que son espace d'adressage logique utilisable en mode User a maintenant une taille de 1,5 Go (en effet, les segments de code et de données ont tous les deux une taille de 1,5 Go et l'utilisation d'un offset supérieur à 1,5 Go produirait une exception de protection générale).
Le programme [DTDUMPER] affiche précisément les descripteurs de segments :
DT Oumper julienikr0.org
GDT size: Ox7F (16 entries), GOT LA: 0xCO3028170
LOT's selector is 0x68 (entry #13 in GDT)
TSS's selector is 0x60 (entry 412 in AIT)
IDT size: Ox7FF (256 entries), IDT LA: 0xCO302000
CS: Ox23 OS: 0x2B SS: Ox2B ES: Ox2B
Dumping AIT (0xCO3028D0)
[...)
(4004) 0x23 60C5F13000000FFFF Code D/B=32bts AVL=0 Present DPL=3 TYPE= _R? BASE=0x600047000 LIMIT=Ox5FFFEFFF
(0005) 0x2B 00C5F3000000FFFF Data D/B=32bts AVL=0 Present DPL=3 TYPE= _40 BASE=0x00000000 LIMIT=Ox5FFFEFFF
L...7
MPROTECT
Nous avons vu deux méthodes, PAGEEXEC et SEGMEXEC, permettant d'obtenir des pages non exécutables, même sur une architecture Intel ne les supportant pas (sur les architectures le supportant nativement, PAGEEXEC utilise bien sûr le support natif du processeur). Les restrictions MPROTECT de PaX permettent d'utiliser ces pages non exécutables afin de contribuer à rendre impossible l'injection de code arbitraire dans l'espace d'adressage d'un processus. Le noyau Linux maintient pour chaque zone de mémoire linéaire (VMA pour Virtual Memory Arca) un ensemble de drapeaux qui déterminent si la zone est exécutable, inscriptible, potentiellement exécutable ou potentiellement inscriptible (c'est-à-dire après un appel à mprotect (7): VM_EXEC, VMVVRITE, VM MAYEXEC, VMMAYWRITE.
Les restrictions MPROTECT font en sorte qu'un VMA ne possède jamais une combinaison d'un drapeau WRITE et d'un drapeau EXEC. Pour cela :
Les mappings anonymes (pile et tas en particulier) ainsi que les mappings de mémoire partagée sont automatiquement marqués VM_WRITEIVM MAYWRITE.
MI Les mappings de fichiers sont marqués avec VM_ WRITEIVM MAYWRITE si la protection PROT_WRITE a été demandée lors de l'appel de rnmap 0 et avec VM_ EXECIVM_MAYEXEC dans le cas contraire.
Une exception existe afin de gérer le cas des fichiers ELF (le plus souvent des bibliothèques) ayant des relocations dans le segment de code (entrée DT_TEXTREL dans la table dynamique) : le linker dynamique doit pouvoir changer les droits d'une page, afin d'effectuer une relocation, puis rechanger les droits. Cette exception peut être supprimée si l'on est certain d'avoir un système ne contenant aucun fichier ELF avec des relocations dans le segment (au sens ELF) exécutable. Nous reviendrons sur ce sujet dans la partie suivante, consacrée à la mise aléatoire de l'espace d'adressage.
Les restrictions MPROTECT ne laissent qu'une possibilité à un attaquant pour introduire du nouveau code exécutable dans un processus dont il aurait pris le contrôle (en utilisant du code existant, à l'aide de return-to-libc enchaînés par exemple) charger en mémoire à l'aide de mmap un fichier dans lequel il a injecté du code, en demandant la protection PROT_EXEC. C'est avec cette même méthode que les bibliothèques utilisées par un programme sont chargées en mémoire. C'est le rôle d'un
système de contrôle d'accès (RSBAC, SELinux, partie RBAC de GrSecurity) de prévenir cette attaque.
Si un tel contrôle d'accès est correctement mis en place, et si l'exception liée aux relocations TEXTREL est supprimée, on peut avec PaX prouver qu'il est impossible d'injecter du code arbitraire dans l'espace d'adressage du processus et cela sans faire aucune hypothèse sur le degré de contrôle d'un attaquant sur le processus !
Même sans la restriction du mappage de fichier avec la protection PROT EXEC, notons que l'attaquant doit pouvoir bénéficier d'un grand contrôle du processus vulnérable (en ne s'appuyant que sur du code déjà présent dans l'espace d'adressage !) pour pouvoir lui faire créer un fichier, lui faire mapper ce fichier avec la protection PROT_EXEC, puis lui faire exécuter son code (évidemment le fichier peut être créé indépendamment, si l'attaquant a déjà un compte local par exemple).
Les restrictions MPROTECT sont évidemment incompatibles avec les quelques programmes générant du code à la volée (par exemple la machine virtuelle JAVA). Pour pallier ce problème, PaX permet de marquer tout fichier exécutable afin de désactiver certaines fonctions, dont MPROTECT.
Address space layout randomization (ASLR)
Comme nous l'avons mentionné, nous nous limitons dans cet article aux techniques permettant à l'attaquant d'avoir un contrôle fort du flot d'exécution du processus. Le contrôle le plus fort est l'exécution de code arbitraire, nous avons vu dans la partie précédente que PaX, s'il est bien utilisé, le prévient totalement.
Cependant il est également possible d'obtenir un contrôle relativement puissant du processus en réutilisant du code existant et en détournant le flot d'exécution. Le degré de contrôle est toutefois sans commune mesure avec celui obtenu avec l'exécution de code arbitraire.
C'est pourquoi des exploits utilisent souvent ces méthodes pour ensuite réussir à exécuter du code arbitraire, par exemple en utilisant mprotect() pour rendre un buffer exécutable puis en retournant sur celui-ci.
Dans le cas de PaX, ceci n'est pas possible, mais il reste quand même souhaitable d'empêcher le contrôle du processus à l'aide de code existant (sans compter que PaX n'est pas toujours utilisé de manière optimale et que les restrictions MPROTECT sont parfois retirées).
L'idée derrière la randomisation de l'espace d'adressage est d'empêcher toute adresse écrite en dur d'avoir un sens. Un exploit fiable doit au maximum éviter les adresses écrites en dur et utiliser des techniques plus avancées ou avoir recours à l'information leak, mais c'est loin d'être toujours possible.
Pour une version compilée donnée d'un programme les adresses dans le fichier ELF (ET_EXEC) sont par exemple considérées comme stables. La randomisation de l'espace d'adressage utilisateur se décompose en trois parties :
il Randomisation de la pile utilisateur (RANDUSTACK)
11 Randomisation des zones mmap()ées (sans adresse fixée) (RANDMMAP)
fi Randomisation de l'exécutable principal (RANDEXEC)
Les deux premières randomisations sont simples (voir [ASLR26] pour une implémentation d'ASLR pur). Pour RANDUSTACK, les bits 4 à 27 sont randomisés (alignement sur 16 octets). Cela est réalisé au niveau de deux fonctions de fs/exec.c responsables de la création de la pile lors de l'appel système execve( Pour RANDMMAP, la fonction arch _get_urimapped_area I) (mn/mmap.c) est modifiée.
Celle-ci est responsable de la recherche d'une zone de mémoire libre lorsque le drapeau MAP FIXED n'a pas été utilisé (c'est-à- dire lorsque l'appelant ne souhaite pas spécifier d'adresse précise où sera créé son mapping, qu'il soit anonyme où d'un fichier). Les bits 12 à 27 sont ici randomisés (alignement sur une page). Grâce à RANDMMAP, les adresses des bibliothèques, du tas lorsque la libc utilise mua() pour le gérer (rare en pratique), et des requêtes manuelles de mappage de fichier ou anonyme qui n'utilisent pas le drapeau MAP_FIXED (c'est presque toujours le cas) sont automatiquement aléatoires.
Cependant, les adresses de l'exécutable principal (ET EXEC) ne sont pas randomisées par RANDMMAP. Comme le tas, lorsqu'il est géré avec brk() (ce qui est courant) débute dans la section .bss, mappée avec l'exécutable principal, celui-ci n'est pas non plus randomisé (en fait une faible randomisation est tout de même ajoutée par PaX en « gâchant » volontairement de l'espace).
Cela est évidemment très gênant, d'autant plus qu'un attaquant peut ainsi retrouver les adresses de fonctions dans les
bibliothèques (comme system()), en utilisant dl -resol ve 0 et
le mécanisme GOT/PLT [Wojtczuk] et passer ainsi outre la randomisation des bibliothèques.
Le problème avec l'exécutable principal est que celui-ci est placé en mémoire à partir d'un fichier ELF, au format ET EXEC qui contient énormément d'adresses « en dur ». De plus ce fichier ne contient pas les informations de relocations nécessaires pour le reloger !
La solution consiste à générer des fichiers exécutables de type ET_DYN (type normalement réservé pour les bibliothèques). La PaX Team a introduit en 2003 une méthode pour générer de tels exécutables [ET_DYN]: en utilisant un crtl.o relogeable
(utilisant la GOT), l'option -shared de GCC, et en spécifiant un
lieur dynamique à utiliser, il est possible de générer un fichier exécutable ET_DYN. Notons que ceci n'était pas révolutionnaire, la libc est déjà un tel exécutable (vous pouvez essayer de l'exécuter !).
La génération de fichiers exécutables ET_DYN est également l'occasion d'éviter les relocations dans le code (cf. TEXTREL mentionnée dans la partie MPROTECT) en utilisant le switch
-fPIC permettant d'obtenir du code indépendant de la position.
Avec ces modifications, il est maintenant possible de randomiser l'exécutable principal : cela est fait dans la fonction load_elf_ binary( du noyau et cela fait partie de l'option RANDMMAP de PaX.
Redhat s'est appuyé sur cette idée pour introduire en 2003
un switch -pi e (position independant executable) à lb (et -f PIE,
similaire à -f PIC, dans GCC), réalisant exactement les opérations décrites plus haut. Redhat a ensuite introduit le switch -z relro
[RELRO] permettant de rassembler toutes les sections qui ne sont disponibles en écriture que pour des raisons de relocation et de créer un program header spécifique (PT_GNU RELRO)
afin que le lieur dynamique puisse s'il le souhaite réaliser un mprotecT.) sur cette zone après que les relocations aient été faites pour la mettre en lecture seule. Grâce à ce changement, on peut rendre la GOT disponible en lecture seule après le chargement du programme.
Lorsque notre système n'utilise que des exécutables de type ET_DYN, nous pouvons être dans une situation de full ASLR, où tout l'espace d'adressage du processus est aléatoire. C'est le cas dans les distributions [Adamantix], Hardened [Gentoo], et en partie dans les dernières Redhat. Il faut cependant garder à l'esprit plusieurs choses
IM Certaines erreurs de programmation permettent de faire du leak d'information, et de donner à l'attaquant des informations précieuses sur l'espace d'adressage, parfois des adresses complètes.
11 est possible d'affaiblir la randomisation à l'aide d'un bourrage avec des instructions de type "cep". Cela est particulièrement réaliste sur la pile où des bits de poids faible (à partir du Sième) sont aléatoires.
MI Tout n'est ici qu'une question de probabilités : si l'attaquant a de la chance ou peut utiliser la force brute aussi longtemps qu'il le souhaite, il finira par réussir. Il faut absolument utiliser des techniques anti-bruteforce, telles que SegvGuard ou celle utilisée dans [GRSecurity].
MI L'espérance mathématique lors d'une tentative de brute force est très différente selon que l'on crashe tout le démon (one-shot) ou seulement un fils. En effet, si les processus que l'on attaque sont tous issus d'un fork() du même démon, l'espace d'adressage est toujours semblable et nous en apprenons un peu plus après chaque tentative
MI Lors d'une attaque locale il est facile d'obtenir des informations sur la mémoire d'un processus, même sans privilèges (par exemple /procipid/maps). Il faut utiliser un patch tel que [PAX+OBS] ou un système de contrôle d'accès pour l'éviter.
Notons pour être complet qu'il existe dans PaX une méthode, appelée RANDEXEC, ingénieuse, mais très coûteuse en performances, permettant de randomiser les exécutables de type ET_EXEC. L'idée repose sur le fait que beaucoup de branchements sont réalisés de manière relative et que, pour ceux- ci, il est possible de reloger l'exécutable principal « tel quel ».
L'exécutable est relogé, mais un miroir (utilisant le VMA-mirroring décrit dans la section SEGMEXEC) existe à l'adresse originale.
Ce miroir à l'adresse originale n'est pas exécutable (cette partie repose sur PAGEEXEC ou SEGMEXEC [notons que dans le cas de SEGMEXEC il n'y aura pas 3 miroirs comme on pourrait s'y attendre car on n'a pas besoin d'accéder aux données de manière relative, et on ne veut pas qu'il soit possible d'accéder au code de manière absolue]).
Les adresses absolues peuvent donc être utilisées pour écrire ou lire des données. Si une adresse absolue est utilisée pour exécuter du code, une exception se produira et PaX va utiliser une heuristique pour déterminer s'il s'agit d'une exécution légitime ou d'une attaque. Cette méthode est simplement une preuve de concept et ne doit pas être réellement utilisée à cause
de la faiblesse de l'heuristique et des coûts en performances. Elle a été retirée dans les dernières version de PaX pour noyaux
2.6.
Le programme [PAXTEST] est capable de vérifier la randomisation par des tests statistiques. On retrouve des valeurs proches des valeurs théoriques (16 bits pour RANDMMAP et 24 pour RANDUSTACK), sauf pour le heap qui bénéficie d'un traitement spécial (randomisation par « perte de place ») :
Anonymous mapping randomisation test : 16 bits (guessed)
Heap randomisation test (ET_EXEC) : 13 bits (guessed)
Heap randomisation test (ET_DYN) : 25 bits (guessed)
Main executable randomisation (ET_EXEC) : No randomisation
Main executable randomisation (ET_DYN) : 17 bits (guessed)
Shared library randomisation test : 16 bits (guessed)
Stock randomisation test (SEGMEXEC) : 23 bits (guessed)
Stock randomisation test (PAGEEXEC) : 24 bits (guessed)
Exec-Shield
Exec-Shield a été annoncé en 2003 par Ingo Molnar, un « kerne! hacker » réputé de chez Redhat. À son annonce, exec-shield était une évolution d'OpenWall, dont l'idée était d'avoir une limite dynamique (au lieu de statique) pour le segment de code, correspondant pour tout processus à l'adresse mémoire exécutable la plus haute de son espace d'adressage, ce qui permet, contrairement à OpenWall, de rendre non exécutables d'autres zones que la pile.
De plus, Exec-Shield utilise également l'armure ASCII et y place les mappings PROT_EXEC (donc les bibliothèques), afin de rendre potentiellement plus difficiles certains return-to-libc et de concentrer le maximum de code exécutable le plus bas possible afin d'avoir le segment de code le plus petit possible. Voici les descripteurs de segment d'un processus [DTDUMPER] et son fichier maps (noyau 2.4), on peut voir la limite d'exécutabilité à 0x804A000:
DT Dumper
julien(kr0.org
(#4) 0x23 0000F80000008049 Code (1/8=32bts AVL=0 Present DPL=3 TYPE= cRA BASE=0;(00000000 LIM1T=0)(08049EFF
(#5) 0x28 00CFF3000000FFFF Data 0/8=32bts AVL=0 Present DPL=3 TYPE= eNA 8ASE=0x00000000 L1MIT=41xEFFEFFFF
ohlumi-undoea r•xp 00000000 03:02 755565 llibllibc-2.3.2.sa
00d3de00 eed45000 rw-p 05127000 02:02 765565 iiibulibc-2.3.2.so
00d45000-00d484510 rw•p 00000000 00:50 0
00d5a000-00d70000 r-xp 00000000 01:02 765562 /libild-2.3.2.so
00d70000-00d71000 rw-p 00015000 03:02 765562 /1ib/ld-2.3.2.ss
08048000-0804a000 r-xp 00000000 03:02 162889 iroot/esidtdurnper
0804a000-0804b000 rw-p 00001000 03:02 162889 /rootiesidtdumper
09da3000-09dc4000 rw-p 00000000 00:00 0
2000000-200b5000 ru-p 00000000 00:00 0
bffad000-c0000000 rw-p fffb8000 00:00 0
Depuis 2003, Exec-Shield a légèrement évolué. Outre quelques corrections de bugs exploitables, il a repris l'idée de randomisation utilisée dans PaX et tire en particulier profit des exécutables ET
DYN générés à l'aide des switches -fPl E et -pie que Redhat utilise
de plus en plus pour générer les exécutables « sensibles ».
Il est intéressant de noter qu'Exec-Shield a été voulu comme un patch simple et léger, la sécurité n'étant pas la préoccupation fondamentale de son auteur. L'approche d'Exec-Shield a de nombreuses limites :
11/1 On n'a pas de réelle sémantique des pages non exécutables, cela se traduit par un problème important : certaines zones de données seront situées sous la limite du segment de code et seront donc exécutables. En particulier, toutes les zones correspondant aux sections .data ou .bss des bibliothèques seront donc exécutables. Pour certains programmes, cela peut être bien pire si une zone exécutable est chargée à des adresses hautes pour une raison ou pour une autre.
Exec-Shield a longtemps été incompatible avec le vDSO
(1 i nux-gate. soi., le système introduit dans Linux 2.6
permettant de tirer parti des appels système rapides de Intel à l'aide de sysenter/sysexit) : en effet, il s'agit d'une zone exécutable située tout en haut de l'espace d'adressage, ce qui est incompatible avec une limite basse pour le segment de code. Cependant, en juin 2005, Roland McGrath a proposé un patch grâce auquel il est possible de reloger le vDSO, qui est donc maintenant relogé et même randomisé dans l'ASCII armor comme une bibliothèque normale.
Il n'y a pas d'équivalent aux restrictions MPROTECT de PaX : un attaquant ayant acquis un certain degré de contrôle du processus (par exemple à l'aide de returnto-libc enchaînés) peut faire un mprotect() afin de rendre la pile (et donc tout l'espace d'adressage car la pile est située en haut !) exécutable ! Contrairement à PaX qui distingue complètement l'ASLR de l'anti-injection de code exécutable, Exec-Shield repose indirectement sur l'ASLR pour rendre potentiellement plus difficile (et non pas impossible) l'injection de code exécutable.
MI La randomisation dans Exec-Shield est plus faible que celle de PaX, notamment à cause du fait que les bibliothèques et l'exécutable principal doivent êtres « groupés » sous la limite du segment de code :
Anonymous mapping randomisation test : 8 bits (guessed)
Heap randomisation test (ET_EXEC) 13 bits (guessed)
Heap randomisation test (ET_DYN) : 13 bits (guessed)
Main executable randomisation (ET_EXEC) : No randomisation
Main executable randomisation (ET_DYN) : 12 bits (guessed)
Shared library randomisation test : 12 bits (guessed)
Stack randomisation test : 19 bits (guessed)
La « fonctionnalité » la plus controversée introduite par Exec-Shield est sans nul doute PT_GNU_STACK. Les autres fonctionnalités s'inscrivent bien dans le modèle « faire le plus possible dans un patch simple et léger sans trop se soucier de la sécurité », mais PT_GNU_STACK introduit de réels problèmes et est un non-sens du point de vue de la sécurité. PT_GNU_STACK est un drapeau du program header permettant de marquer un fichier ELF (programme ou bibliothèque) comme « ayant besoin d'une pile exécutable ».
Ce marquage est réalisé de manière automatique par la toolchain (GCC, Id,...) en détectant des constructions ayant besoin d'une pile exécutable (en pratique, PT_GNU_STACK se limite aux trampolines GCC). Le lieur dynamique tire parti de cette information lors du chargement du programme principal ou d'une bibliothèque pour, si l'un au moins de ces composants réclame une
pile exécutable, appeler la fonction make_stack_executable0-
Le problème de cette approche est qu'il y a inévitablement des faux positifs et des faux négatifs. De plus, un lieur dynamique
supportant cette fonctionnalité est incompatible avec PaX, puisque PaX verrouille à l'execve() les « privilèges » (droit de mprotect PROT_EXEC par exemple) du processus, alors que PT_GNU_STACK est par essence dynamique.
Avec un lieur dynamique supportant PT_GNU_STACK, si une bibliothèque marquée comme utilisant une pile exécutable est chargée, la pile sera alors marquée exécutable (le processus et donc l'attaquant, a le droit de rendre la pile exécutable dans le modèle PT_GNU_STACK), PaX l'interdira, ce qui conduira le lieur dynamique à abandonner le chargement.
Notons aussi que l'existence même d'une fonction "make_ stack_executable )' est hallucinante [i] puisque cette fonction devient évidemment la cible privilégiée pour un attaquant capable d'appeler du code de son choix existant dans le processus : en effet, comme nous l'avons mentionné, rendre la pile exécutable sous exec-shield le désactive complètement. Nous voyons là encore qu'exec-shield repose complètement sur sa randomisation (dont la limitation à 12 bits pour les bibliothèques est ici particulièrement ennuyeuse).
Malgré ses nombreuses limitations, grâce à la notoriété dingo Molnar, Exec-Shield fait peu à peu son entrée dans le noyau Linux. La version 2.6.12 du noyau a été la première à inclure une version allégée de la randomisation de l'espace d'adressage d'Exec-Shield (désactivable avec /proc/sys/kernel/randomize_va space). Voici le résultat de [paxtest]
Anonymous mapping randomisation test : 8 bits (guessed)
Meap randomisation test (ET_EXEC) : Na randomisation
Heap randomisation test (ET_DYN) : No randomisation
Main executable randomisation [ET_EXEC) : No randomisation
Main executable randomisation (ET_DYN) : Ho randomisation
Shared library randomisation test : 10 bits (guessed)
Stock randomisation test : 19 bits (guessed)
OpenBSD's WAX
WAX (W xor X) est apparu dans OpenBSD 3.3 en mai 2003. H a pour but d'empêcher l'existence de pages à la fois disponibles en écriture et en exécution. Dans la version 3.3 d'OpenBSD, WAX ne fonctionnait que sur les processeurs supportant le marquage non exécutable d'une page. WAX pour i386 est apparu avec la version 3.4 d'OpenBSD en novembre 2003.
L'approche d'OpenBSD est de ne réaliser que des modifications simples dans le noyau et de réaliser le gros du travail en useriand. Là encore, sur i386, la segmentation est utilisée : le segment de codes une limite à 512 Mo, ce qui signifie que toute adresse (dans l'espace logique) supérieure à 0x20000000 ne sera pas exécutable.
La première étape pour la réalisation de WAX (utile même sur les processeurs ayant un marquage NX) a été de s'arranger pour ne plus avoir besoin de zones à la fois exécutables et disponibles en écriture. Pour cela le trampoline 'sigreturn' a été sorti de la pile (contrairement à ce qui a été fait sous Linux, il n'est cependant pas intégré dans la libc sous OpenBSD) et la GOT et la PLT ne sont plus disponibles en écriture (le linker met/retire les protection à l'exécution selon les besoins, cette approche est à mettre en parallèle avec celle de [RELRO]).
D'autres assainissements ont également été réalisés, par exemple les sections .ctors et .dtors qui contiennent des pointeurs ne sont plus disponibles en écriture et la section .radota n'est plus exécutable : vous pouvez consulter à ce sujet les slides de Theo [WAX].
La deuxième étape dans la réalisation de VVAX sous i386 fut de s'arranger pour mapper toutes les zones de données au-dessus de la limite des 512 Mo et toutes les zones exécutables en dessous. De nombreux changements ont été réalisés au niveau du chargeur dynamique pour rendre possible ce mappage séparé, où les bibliothèques et l'ELF principal ne sont plus chargés d'un bloc.
OpenBSD utilise égaiement un ASLR similaire à celui de PaX, mais plus limité. Voici le résultat de [PAXTEST]
Anonymous mapping randomisation test : 20 bits (guessed)
Heap randomisation test (ET_EXEC) : No randomisation
Main executable randomisation (ET_EXEC) : No randomisation
Shared iibrary randomisation test : 16 bits (guessed)
Steck randomisation test : 15 bits (guessed)
Comme Exec-Shield, OpenBSD n'offre pas d'équivalent aux restrictions MPROTECT de PaX. De ce fait, le processus (et donc potentiellement l'attaquant !) est autorisé à rendre une page exécutable. Nous avons également pu remarquer à l'aide de [DTIDUMPER] que plusieurs entrées dans la LDT et la GDT sont des segments de code parfaitement valides pour exécuter du code n'importe où dans la partie utilisateur de l'espace d'adressage (les limites Ox I FFFFFFF correspondent à la limite de 512 Mo alors que les limites OxCFBFDFFF couvrent tout l'espace utilisateur).
Cela constitue un énorme trou de sécurité puisqu'il suffit de réaliser un branchement inter-segment (far cal!, far jump, far ret,...) afin d'exécuter du code dans une zone censée être non exécutable I Voici les segments de code utilisables :
GT Dumper
julien@cd.org
DDT size: OxFFFF 18192 entries), DDT LA: 0xE7825000 LOT's selector is 0x18 (entry #3 in GDT)
ISSis selector is 0x168 (entry #45 in SOT)
10T size: Ox7FF 1256 entries), LOT LA: 41x005CEF6O CS: 0x1F OS: 0x27 SS: 0x27 ES: 0x27
Dumping GUT (OxE78250001
100041 0x23 OOCCF8000000FBFD Code 0/8=32bts AVL=0 Present DPL=3 TYPE= cRA BASE=Ox08061000 LIMIT=00CFBEDFFF
(0005) 0x28 00C1FBOO8888FFFF Code 0/8=32bts AVL=0 Present DPL=3 TYPE= cRA 9ASE=8x00000000 LIMIT=0x1FEFFFFF
[...]
Dumping LOT (0x005CEECO)
(0002) 8x17 00CCF8000000FBED Code 0/13=32bts A9L=0 Present DPL=3 TYPE= cRA BASE=Ox0000O000 LIMIT=OxCFBEDFFF
(01103) Ox1F 00C1F8000000FFFF Code D/B=32bts AVL-0 Present DPL=3 TYPE= oPA 8ASE=0x001100000 LIMIT=Ox1FFEFFFF
[...
donc très simple de le trouver dans des zones exécutables à adresse fixe (afin de ne pas avoir à déjouer l'ASLR) : par exemple la section .text de l'exécutable principal (OpenBSD ne propose pas de système d'exécutables relogeables ET_DYN).
Il suffit donc en cas de stack overflow de préparer la pile : le descripteur Ox23 (ou 0x17), l'adresse de retour classique (qui peut être l'adresse du shellcode si on la connaît ou quelque chose de plus compliqué..), l'adresse d'un octet OxCB. Le code ci-dessous [RETF_ DEMO] simule cette situation (en construisant la pile à l'aide de push au lieu d'un overflow) avec un shellcode sur la pile et un retour sur un jmp esp :
; This would be in our target program
fret db OxCB
runstack:
jmp esp ; obviously we execute code on the stack
; Entry point global _start _start:
; This is our payload
push OxFEES shellcode (this is jmp -2)
push 9x17 sur segment selector
push runstack this is the classic return address
don't expect to have a jmp esp in real-life though ;l push fret finding a static offset with BxCB in standard ELF's .text
is very easy
; This is the standard ret after vie control the stack ret
Quand la fonction vulnérable retourne, l'exécution reprend sur un far ret, ce qui a pour conséquence de retourner sur l'adresse runstack tout en chargeant le sélecteur de segment sur la pile (0x17) dans CS. runstack peut être l'adresse de tout code menant à l'exécution de notre shellcode (par exemple directement l'adresse du shellcode, si on la connaît !).
Notons toutefois que pour le cas précis des débordements sur la pile, un autre dispositif de prévention est utilisé dans OpenBSD : Propolice [SSP],
Comparaison
Nous avons vu plusieurs techniques qui essaient d'empêcher l'injection de code arbitraire dans un processus. Nous avons vu que parmi celles-ci, l'approche de PaX est la seule qui permette de réellement prouver qu'il n'est pas possible d'injecter du code (en dehors du rninap( PROT_EXEC d'un fichier, à gérer via un système de contrôle d'accès).
L'approche de PaX consiste à ne pas faire d'hypothèse sur le degré de contrôle de l'attaquant et d'empêcher le processus lui-même d'injecter du nouveau code à l'exécution, c'est une application du principe de moindre privilège. Dans PaX empêcher l'attaquant de détourner le flot d'exécution du programme se fait à l'aide de la randomisation qui est une fonctionnalité à différencier complètement de la partie anti-injection de code.
PaX est particulièrement efficace lorsqu'il est utilisé au sein du framework [GRSECURITY] qui apporte certaines des fonctionnalités manquantes : anti info-ieak, anti bruteforce et système de TPE (Trusted Poth Executable) ou RBAC (Rode Based Access Control) pour lutter contre les mmapf PROT_EXEC de fichiers contrôlés par l'attaquant.
À l'opposé, OpenBSD et Exec-Shield laissent à l'application le privilège de pouvoir injecter du code exécutable et s'appuient sur la randomisation pour éviter le détournement du flot d'exécution du programme (qui une fois acquis par l'attaquant peut donc être utilisé pour injecter du code exécutable).
Cette approche est surtout efficace lorsque l'attaquant a un faible contrôle du flot d'exécution (par exemple en cas de heap overflow), mais est très limitée avec un contrôle plus fort permettant d'enchaîner les appels de fonctions (par exemple en cas de débordement sur la pile).
Cette approche est également limitée par l'existence de potentiels bugs permettant l'information leaking (ou features, cf. /proc/pid/maps en local sous Linux) et oblige à se défendre contre le bruteforce pour rester efficace. En revanche, OpenBSD comme Exec-Shield y gagnent beaucoup en simplicité du code du noyau.
Autres considérations
Protection d'un noyau
Ce thème avait déjà été évoqué à une Rump Session de SSTIC [SECULINUX]. À l'heure actuelle, lorsqu'un pirate a déjà un accès local à une machine sous GNU/Linux et désire élever ses privilèges, les failles du noyau sont souvent la meilleure option. La plupart des binaires privilégiés sont largement audités et au fil du temps de mieux en mieux sécurisés.
Au contraire, le noyau Linux est en développement constant, le code est complexe et rarement audité et de nombreuses vulnérabilités sont présentes dans ses millions de lignes de code. D'autant plus qu'en mode noyau l'erreur ne pardonne vraiment pas : plus que jamais le moindre bug peut se traduire en faille de sécurité.
Pour ajouter encore à cela, le paradigme est complètement différent lorsque l'on essaie d'exploiter un bug du noyau : on possède déjà l'exécution de code arbitraire en mode utilisateur et l'on veut simplement augmenter ses privilèges. Cela signifie que l'on peut créer un processus dont on contrôle parfaitement l'espace adressage en mode utilisateur et que l'on peut exécuter le code de notre choix pour créer la situation où la faille pourra être exploitée.
Par exemple, les déréférences de pointeurs NULL qui sont, lorsqu'on exploite un programme en mode user, difficilement exploitables pour le commun des mortels [DELALLEAU] deviennent exploitables très facilement puisqu'on contrôle la partie basse de l'espace d'adressage.
Protéger le noyau avec des méthodes classiques telles que rendre la pile noyau non exécutable, rendre son adresse aléatoire (RANDKSTACK dans PaX) ou recompiler le noyau avec [SSP] (OpenBSD) ne sert en général à rien : presque toute erreur de programmation dans le noyau est une vulnérabilité et il est difficile de les classifier.
On voit finalement très peu d'erreurs classiques telles que les débordements de tampon. De plus comme le code du noyau est le plus privilégié, il n'est pas possible d'appliquer une politique de « moindre privilège » comme le fait PaX pour les processus en mode utilisateur : on ne peut pas empêcher un noyau compromis d'effectuer certaines opérations.
Il est donc actuellement très difficile d'empêcher un attaquant d'exploiter des failles dans son noyau. La seule méthode est sans douce de recourir à un système de contrôle d'accès ou un TPE (Trusted Path Execution) pour empêcher les démons et les utilisateurs locaux d'exécuter du code qui n'a pas été préalablement validé par l'administrateur.
Là encore le changement de paradigme est lourd de conséquences on doit sans doute veiller à interdire l'utilisation d'interpréteurs (Perl, Python, Ruby...) qui permettent plus ou moins à leur utilisateur d'exécuter du code arbitraire (en tout cas d'avoir un grand degré de contrôle du processus de l'interpréteur).
Il faut également voir que dans cette situation, tout exécutable autorisé par l'administrateur devient privilégié (puisqu'il est autorisé à exécuter du code), et devient donc une cible pour l'attaquant : un bug exploitable dans ibinfls donne à l'attaquant
l'occasion d'exécuter du code arbitraire et d'exploiter une faille noyau. Cette situation est délicate, car tous ces programmes n'ont pas été conçus pour être considérés comme privilégiés et sont très rarement audités.
Le cas de Windows
Il est difficile pour un éditeur différent de Microsoft de protéger l'espace d'adressage car cela nécessite des modifications au coeur du système. Microsoft a cependant, avec Windows XP SP2 et Windows server 2003, commencé à introduire quelques éléments de prévention générique. Outre les modifications au niveau du compilateur, proches de SSP/Propolice, on peut notamment citer l'utilisation du flag NX lorsqu'il est présent afin de rendre certaines zones de données non exécutables. Il n'y a pas cependant sous Windows de mécanisme qui correspondrait aux restrictions niprotectO de PaX.
Cependant, on voit apparaître depuis environ un an des solutions H IPS. Si certaines comme Ozone ou VVhentrust font de l'ASLR, la plupart des logiciels de « gros » éditeurs (CISCO CSA, McAfee Entercept) sont des systèmes de contrôle d'accès.
La mise aléatoire de l'espace d'adressage souffre de plusieurs limitations importantes sous Windows : outre la possibilité pour un attaquant, commune à tous les OS, de réaliser de l'information leaking, il est très difficile de reloger certaines DLL, notamment ntd11.d11 et kerne132.dli à cause du processus d'amorçage de Windows. De plus, les exécutables PE n'ont pas les informations de relocation nécessaires et ne peuvent donc pas être relogés.
Dans ces conditions, l'attaquant possède au moins une source fiable d'opcodes à adresses fixes (kerne132 et ntdll étant susceptibles d'évoluer avec les services packs), et les exploits fiables sous Windows n'utilisent depuis longtemps pas directement l'adresse présumée de la pile : une technique courante en cas de stack overflow est d'écraser une structure EXCEPTION_REGISTRATION d'un Frame-Bosed exception Handler et d'écraser le pointeur vers le handler avec une adresse contenant un jrnp ebx (depuis Windows XP SPI, EBX ne pointe plus vers la structure EXCEPTION REGISTRATION, cependant un pointeur est toujours présent sur la pile à esp+8, et on peut donc utiliser des instructions telles que pop xxx; pop yyy; rot présentes dans notre PE).
Windows XP SP2 vérifiant maintenant que le handler appelé est enregistré (ou qu'il n'est pas dans un module PE avec un Load Conpguration directory) ces techniques évoluent de plus en plus.
Les systèmes de contrôle d'accès (appelés souvent systèmes de détection comportementale sous Windows), sont utilisés de manière classique pour confiner un processus contrôlé par l'attaquant, mais aussi pour empêcher l'appel d'un NSS (Native System Service, les appels système sous Windows) ou d'une fonction d'une bibliothèque depuis des zones jugées anormales comme la pile.
Le paradigme dans lequel travaillent ces logiciels est que l'attaquant a déjà l'exécution de code arbitraire et qu'il faut essayer de le détecter le plus tôt possible. Pour cela, des hooks sont réalisés au niveau de certaines bibliothèques et des NSS et une analyse sont réalisés afin de déterminer si la situation est saine : par exemple ces produits peuvent vérifier que l'adresse de retour n'est pas située dans la pile.
L'exécution de code arbitraire, même sans pouvoir appeler- de fonction de bibliothèques ou de NSS permet déjà de faire énormément de choses.
On peut obtenir beaucoup d'informations sur l'espace d'adressage du processus exploité en utilisant le TEB et le PEB que l'on peut retrouver via le sélecteur de segment fs et un offset fixe (ce qui rend la randomisation du TEB/PEB (par rapport au segment ds) réalisée par Windows XP SP2 inutile une fois l'exécution de code arbitraire acquise par l'attaquant). Ces informations peuvent ensuite être exploitées :
On peut recopier son shellcode dans une zone mémoire jugée « saine » (en général, cela nécessite un peu de travail car une zone mémoire disponible en écriture ne devrait pas être jugée saine).
On peut mettre à profit du code existant dans l'espace d'adressage afin qu'il réalise les appels de fonctions pour nous, tout en réalisant un « lifting » approprié de la pile pour simuler une situation normale.
Références
MB On peut, si l'on sait d'avance quel HIPS on veut contourner « suivre les hooks » pour retrouver la fonction originale.
Ces techniques sont toujours utilisables, mais sont plus ou moins complexes à mettre en œuvre selon la qualité de l'heuristique du produit à contourner.
Il faut également noter que les hooks de bibliothèques en mode user peuvent être contournés en appelant directement les NSS (pour rester générique, on peut utiliser NTDLL.DLL pour en retrouver les numéros).
Cordialement
L'équipe Parisdepannage.fr
Hors ligne
Pages: 1
- Accueil forums
- » Programmation
- » Fiabilisation d'expoit Windows - de la faille à l'expoit (2)
2008 Parisdepannage |Plan du site|Forums |Blog|Lexique ![]()